view flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java @ 401:34de11dcf355

Small bugfix in the adapted WQ state and further i18n strings. flys-artifacts/trunk@1836 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Thu, 05 May 2011 12:34:53 +0000
parents aae8f327425e
children c6b7ca0febcb
line wrap: on
line source
package de.intevation.flys.artifacts.model;

import java.io.Serializable;

import de.intevation.flys.model.River;
import de.intevation.flys.model.Wst;
import de.intevation.flys.model.WstColumn;

import de.intevation.flys.artifacts.cache.CacheFactory;

import de.intevation.flys.backend.SessionHolder;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Collections;

import org.apache.log4j.Logger;

import org.apache.commons.math.analysis.interpolation.SplineInterpolator;

import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;

import org.apache.commons.math.ArgumentOutsideDomainException;

import org.hibernate.Session;
import org.hibernate.Query;
import org.hibernate.SQLQuery;

import org.hibernate.type.StandardBasicTypes;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

public class WstValueTable
implements   Serializable
{
    private static Logger log = Logger.getLogger(WstValueTable.class);

    public static final String CACHE_NAME = "wst-value-table";

    // TODO: put this into a property file
    public static final String SQL_POS_WQ = 
        "SELECT position, w, q, column_pos" +
        "    FROM wst_value_table"          +
        "    WHERE wst_id = :wst_id";

    public static final int DEFAULT_Q_STEPS = 500;

    public static class Column
    implements          Serializable
    {
        protected String name;

        public Column() {
        }

        public Column(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
    // class Column

    public static class Row
    implements          Serializable, Comparable<Row>
    {
        double    km;
        double [] ws;
        double [] qs;
        boolean   qSorted;

        public Row() {
        }

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

        public Row(double km, double [] ws, double [] qs) {
            this(km);
            this.ws = ws;
            this.qs = qs;
        }

        public static final double linear(
            double x,
            double x1, double x2,
            double y1, double y2
        ) {
            // y1 = m*x1 + b
            // y2 = m*x2 + b
            // y2 - y1 = m*(x2 - x1)
            // m = (y2 - y1)/(x2 - x1) # x2 != x1
            // b = y1 - m*x1

            if (x1 == x2) {
                return 0.5*(y1 + y2);
            }
            double m = (y2 - y1)/(x2 - x1);
            double b = y1 - m*x1;
            return x*m + b;
        }

        public static final double factor(double x, double p1, double p2) {
            // 0 = m*p1 + b <=> b = -m*p1
            // 1 = m*p2 + b
            // 1 = m*(p2 - p1)
            // m = 1/(p2 - p1) # p1 != p2
            // f(x) = x/(p2-p1) - p1/(p2-p1) <=> (x-p1)/(p2-p1)

            return p1 == p2 ? 0.0 : (x-p1)/(p2-p1);
        }

        public static final double weight(double factor, double a, double b) {
            return (1.0-factor)*a + factor*b;
        }

        public double getWForKM(Row other, int index, double km) {
            double w1  = ws[index];
            double w2  = other.ws[index];
            double km1 = this.km;
            double km2 = other.km;
            return linear(km, km1, km2, w1, w2);
        }

        public void interpolateW(
            Row       other,
            double    km,
            double [] input,
            double [] output
        ) {
            double factor = factor(km, this.km, other.km);

            for (int i = 0; i < input.length; ++i) {
                double q = input[i];
                int idx1 =       getQIndex(q);
                int idx2 = other.getQIndex(q);

                double w1 = idx1 >= 0
                    ? ws[idx1]
                    : interpolateW(-idx1-1, q);

                double w2 = idx2 >= 0
                    ? other.ws[idx2]
                    : other.interpolateW(-idx2-1, q);

                output[i] = weight(factor, w1, w2);
            }
        }

        public double interpolateW(int idx, double q) {
            return idx < 1 || idx >= qs.length
                ? Double.NaN // do not extrapolate
                : linear(q, qs[idx-1], qs[idx], ws[idx-1], ws[idx]);
        }

        public double interpolateW(double q) {
            if (Double.isNaN(q)) return Double.NaN;
            int index = getQIndex(q);
            return index >= 0 ? ws[index] : interpolateW(-index -1, q);
        }

        public int getQIndex(double q) {
            return qSorted ? binaryQIndex(q) : linearQIndex(q);
        }

        public int binaryQIndex(double q) {
            return Arrays.binarySearch(qs, q);
        }

        public int linearQIndex(double q) {
            switch (qs.length) {
                case 0: break;
                case 1:
                    if (qs[0] == q) return 0;
                    if (qs[0] <  q) return -(1+1);
                    return -(0+1);
                default:
                    for (int i = 1; i < qs.length; ++i) {
                        double qa = qs[i-1];
                        double qb = qs[i];
                        if (qa == q) return i-1;
                        if (qb == q) return i;
                        if (qa > qb) { double t = qa; qa = qb; qb = t; }
                        if (q > qa && q < qb) return -(i+1);
                    }
                    return -qs.length;
            }

            return -1;
        }

        public int compareTo(Row other) {
            double d = km - other.km;
            if (d < 0.0) return -1;
            if (d > 0.0) return +1;
            return 0;
        }

        public double [][] cloneWQs() {
            return new double [][] {
                (double [])ws.clone(),
                (double [])qs.clone() };
        }

        public double [][] interpolateWQ(Row other, double km, int steps) {

            int W = Math.min(ws.length, other.ws.length);

            if (W < 1) {
                return new double[2][0];
            }

            double factor = factor(km, this.km, other.km);

            double [] splineQ = new double[W];
            double [] splineW = new double[W];

            double minQ =  Double.MAX_VALUE;
            double maxQ = -Double.MAX_VALUE;

            for (int i = 0; i < W; ++i) {
                double wws = weight(factor, ws[i], other.ws[i]);
                double wqs = weight(factor, qs[i], other.qs[i]);
                splineW[i] = wws;
                splineQ[i] = wqs;
                if (wqs < minQ) minQ = wqs;
                if (wqs > maxQ) maxQ = wqs;
            }

            double stepWidth = (maxQ - minQ)/steps;

            SplineInterpolator interpolator = new SplineInterpolator();
            PolynomialSplineFunction spline =
                interpolator.interpolate(splineQ, splineW);

            double [] outWs = new double[steps];
            double [] outQs = new double[steps];

            try {
                double q = minQ;
                for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
                    outWs[i] = spline.value(outQs[i] = q);
                }
            }
            catch (ArgumentOutsideDomainException aode) {
                log.error("Spline interpolation failed.", aode);
            }

            return new double [][] { outWs, outQs };
        }

        public double [][] interpolateWQ(int steps) {

            int W = ws.length;

            if (W < 1) {
                return new double[2][0];
            }

            double [] splineW = new double[W];
            double [] splineQ = new double[W];

            double minQ =  Double.MAX_VALUE; 
            double maxQ = -Double.MAX_VALUE; 

            for (int i = 0; i < W; ++i) {
                splineW[i] = ws[i];
                splineQ[i] = qs[i];
                if (qs[i] < minQ) minQ = qs[i];
                if (qs[i] > maxQ) maxQ = qs[i];
            }

            double stepWidth = (maxQ - minQ)/steps;

            SplineInterpolator interpolator = new SplineInterpolator();

            PolynomialSplineFunction spline =
                interpolator.interpolate(splineQ, splineW);

            double [] outWs = new double[steps];
            double [] outQs = new double[steps];

            try {
                double q = minQ;
                for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
                    outWs[i] = spline.value(outQs[i] = q);
                }
            }
            catch (ArgumentOutsideDomainException aode) {
                log.error("Spline interpolation failed.", aode);
            }

            return new double [][] { outWs, outQs };
        }

        public int ascendingWs() {
            if (ws.length < 2) {
                return ws.length;
            }

            int idx = 1;

            for (; idx < ws.length; ++idx) {
                if (Double.isNaN(ws[idx]) || ws[idx] < ws[idx-1]) {
                    return idx;
                }
            }

            return idx;
        }

        public double [][] weightWQs(Row other, double km) {
            int W = Math.min(ws.length, other.ws.length);
            double factor = factor(km, this.km, other.km);

            double [] outWs = new double[W];
            double [] outQs = new double[W];

            for (int i = 0; i < W; ++i) {
                outWs[i] = weight(factor, ws[i], other.ws[i]);
                outQs[i] = weight(factor, qs[i], other.qs[i]);
            }

            return new double [][] { outWs, outQs };
        }
    }
    // class Row

    protected List<Row> rows;

    protected Column [] columns;

    public WstValueTable() {
        rows = new ArrayList<Row>();
    }

    public WstValueTable(Column [] columns) {
        this();
        this.columns = columns;
    }

    public double [] interpolateW(double km, double [] qs) {
        return interpolateW(km, qs, new double[qs.length]);
    }

    public double [] interpolateW(double km, double [] qs, double [] ws) {

        int rowIndex = Collections.binarySearch(rows, new Row(km));

        if (rowIndex >= 0) { // direct row match
            Row row = rows.get(rowIndex);
            for (int i = 0; i < qs.length; ++i) {
                ws[i] = row.interpolateW(qs[i]);
            }
        }
        else { // needs bilinear interpolation
            rowIndex = -rowIndex -1;

            if (rowIndex < 1 || rowIndex >= rows.size()) {
                // do not extrapolate
                Arrays.fill(ws, Double.NaN);
            }
            else {
                rows.get(rowIndex-1).interpolateW(
                    rows.get(rowIndex),
                    km, qs, ws);
            }
        }

        return ws;
    }


    public double [][] interpolateWQ(double km) {
        return interpolateWQ(km, DEFAULT_Q_STEPS);
    }

    public double [][] interpolateWQ(double km, int steps) {

        int rowIndex = Collections.binarySearch(rows, new Row(km));

        if (rowIndex >= 0) { // direct row match
            Row row = rows.get(rowIndex);
            return row.interpolateWQ(steps);
        }

        rowIndex = -rowIndex -1;

        if (rowIndex < 1 || rowIndex >= rows.size()) {
            // do not extrapolate
            return new double[2][0];
        }

        Row r1 = rows.get(rowIndex-1);
        Row r2 = rows.get(rowIndex);

        return r1.interpolateWQ(r2, km, steps);
    }

    public static WstValueTable getTable(River river) {
        return getTable(river, 0);
    }

    public static final class CacheKey
    implements                Serializable
    {
        private int riverId;
        private int kind;

        public CacheKey(int riverId, int kind) {
            this.riverId = riverId;
            this.kind    = kind;
        }

        public int hashCode() {
            return (riverId << 8) | kind;
        }

        public boolean equals(Object other) {
            if (!(other instanceof CacheKey)) {
                return false;
            }
            CacheKey o = (CacheKey)other;
            return riverId == o.riverId && kind == o.kind;
        }
    } // class CacheKey

    public static WstValueTable getTable(River river, int kind) {

        Cache cache = CacheFactory.getCache(CACHE_NAME);

        CacheKey cacheKey;

        if (cache != null) {
            cacheKey = new CacheKey(river.getId(), kind);
            Element element = cache.get(cacheKey);
            if (element != null) {
                log.debug("got wst value table from cache");
                return (WstValueTable)element.getValue();
            }
        }
        else {
            cacheKey = null;
        }

        WstValueTable valueTable = getTableUncached(river, kind);

        if (cacheKey != null) {
            log.debug("store wst value in cache");
            Element element = new Element(cacheKey, valueTable);
            cache.put(element);
        }

        return valueTable;
    }

    public static WstValueTable getTableUncached(River river, int kind) {

        Session session = SessionHolder.HOLDER.get();

        Query query = session.createQuery(
            "from Wst where river=:river and kind=:kind");
        query.setParameter("river", river);
        query.setInteger("kind",    kind);

        List<Wst> wsts = query.list();

        if (wsts.isEmpty()) {
            return null;
        }

        Wst wst = wsts.get(0);

        // TODO: Do this sorting at database level
        List<WstColumn> wstColumns = new ArrayList(wst.getColumns());
        Collections.sort(wstColumns, new Comparator<WstColumn>() {
            public int compare(WstColumn a, WstColumn b) {
                int pa = a.getPosition();
                int pb = b.getPosition();
                if (pa < pb) return -1;
                if (pa > pb) return +1;
                return 0;
            }
        });

        Column [] columns = new Column[wstColumns.size()];
        for (int i = 0; i < columns.length; ++i) {
            columns[i] = new Column(wstColumns.get(i).getName());
        }

        // using native SQL here to avoid myriad of small objects.
        SQLQuery sqlQuery = session.createSQLQuery(SQL_POS_WQ)
            .addScalar("position",   StandardBasicTypes.DOUBLE)
            .addScalar("w",          StandardBasicTypes.DOUBLE)
            .addScalar("q",          StandardBasicTypes.DOUBLE)
            .addScalar("column_pos", StandardBasicTypes.INTEGER);

        sqlQuery.setInteger("wst_id", wst.getId());

        WstValueTable valueTable = new WstValueTable(columns);

        int lastColumnNo = -1;
        Row row          = null;

        Double lastQ     = -Double.MAX_VALUE;
        boolean qSorted  = true;

        for (Object  r: sqlQuery.list()) {
            Object[] result = (Object[]) r;

            double km    = (Double) result[0];
            Double w     = (Double) result[1];
            Double q     = (Double) result[2];
            int columnNo = (Integer)result[3];

            if (columnNo > lastColumnNo) { // new row
                if (row != null) {
                    row.qSorted = qSorted;
                    valueTable.rows.add(row);
                }
                row = new Row(
                    km,
                    new double[columnNo+1],
                    new double[columnNo+1]);
                lastQ = -Double.MAX_VALUE;
                qSorted = true;
            }

            row.ws[columnNo] = w != null ? w : Double.NaN;
            row.qs[columnNo] = q != null ? q : Double.NaN;

            if (qSorted && (q == null || lastQ > q)) {
                qSorted = false;
            }
            lastQ = q;

            lastColumnNo = columnNo;
        }

        if (row != null) {
            valueTable.rows.add(row);
        }

        return valueTable;
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org