Mercurial > dive4elements > river
view flys-artifacts/src/main/java/de/intevation/flys/artifacts/model/WstValueTable.java @ 2089:0da8874bd378
Added initial state to map artifact to be able to advance and step back.
The map artifact overrides describe() to have the complete UI information in the
describe response document.
flys-artifacts/trunk@3613 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Raimund Renkert <raimund.renkert@intevation.de> |
---|---|
date | Fri, 06 Jan 2012 12:02:10 +0000 |
parents | 2730d17df021 |
children | 2898b1ff6013 |
line wrap: on
line source
package de.intevation.flys.artifacts.model; import java.io.Serializable; import de.intevation.flys.artifacts.math.Linear; import de.intevation.flys.artifacts.math.Function; import java.util.Arrays; import java.util.ArrayList; 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.apache.commons.math.exception.MathIllegalArgumentException; import gnu.trove.TDoubleArrayList; /** * W, Q and km data from database 'wsts' spiced with interpolation algorithms. */ public class WstValueTable implements Serializable { private static Logger log = Logger.getLogger(WstValueTable.class); public static final int DEFAULT_Q_STEPS = 500; /** * A Column in the table, typically representing one measurement session. */ public static final class Column implements Serializable { protected String name; protected QRangeTree qRangeTree; public Column() { } public Column(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public QRangeTree getQRangeTree() { return qRangeTree; } public void setQRangeTree(QRangeTree qRangeTree) { this.qRangeTree = qRangeTree; } } // class Column /** * A (weighted) position used for interpolation. */ public static final class QPosition { protected int index; protected double weight; public QPosition() { } public QPosition(int index, double weight) { this.index = index; this.weight = weight; } public QPosition set(int index, double weight) { this.index = index; this.weight = weight; return this; } } // class Position public static final class SplineFunction { public PolynomialSplineFunction spline; public double [] splineQs; public double [] splineWs; public SplineFunction( PolynomialSplineFunction spline, double [] splineQs, double [] splineWs ) { this.spline = spline; this.splineQs = splineQs; this.splineWs = splineWs; } public double [][] sample( int numSamples, double km, Calculation errors ) { double minQ = getQMin(); double maxQ = getQMax(); double [] outWs = new double[numSamples]; double [] outQs = new double[numSamples]; Arrays.fill(outWs, Double.NaN); Arrays.fill(outQs, Double.NaN); double stepWidth = (maxQ - minQ)/numSamples; try { double q = minQ; for (int i = 0; i < outWs.length; ++i, q += stepWidth) { outWs[i] = spline.value(outQs[i] = q); } } catch (ArgumentOutsideDomainException aode) { if (errors != null) { // TODO: I18N errors.addProblem(km, "spline interpolation failed"); } log.error("spline interpolation failed.", aode); } return new double [][] { outWs, outQs }; } public double getQMin() { return Math.min(splineQs[0], splineQs[splineQs.length-1]); } public double getQMax() { return Math.max(splineQs[0], splineQs[splineQs.length-1]); } /** Constructs a continues index between the columns to Qs. */ public PolynomialSplineFunction createIndexQRelation() { double [] indices = new double[splineQs.length]; for (int i = 0; i < indices.length; ++i) { indices[i] = i; } try { SplineInterpolator interpolator = new SplineInterpolator(); return interpolator.interpolate(indices, splineQs); } catch (MathIllegalArgumentException miae) { // Ignore me! } return null; } } // class SplineFunction /** * A row, typically a position where measurements were taken. */ public static final class Row implements Serializable, Comparable<Row> { double km; double [] ws; public Row() { } public Row(double km) { this.km = km; } public Row(double km, double [] ws) { this(km); this.ws = ws; } /** * Compare according to place of measurement (km). */ public int compareTo(Row other) { double d = km - other.km; if (d < -0.0001) return -1; if (d > 0.0001) return +1; return 0; } /** * Interpolate Ws, given Qs and a km. * * @param iqs Given ("input") Qs. * @param ows Resulting ("output") Ws. * @param table Table of which to use data for interpolation. */ public void interpolateW( Row other, double km, double [] iqs, double [] ows, WstValueTable table, Calculation errors ) { double kmWeight = Linear.factor(km, this.km, other.km); QPosition qPosition = new QPosition(); for (int i = 0; i < iqs.length; ++i) { if (table.getQPosition(km, iqs[i], qPosition) != null) { double wt = getW(qPosition); double wo = other.getW(qPosition); if (Double.isNaN(wt) || Double.isNaN(wo)) { if (errors != null) { // TODO: I18N errors.addProblem( km, "cannot find w for q = " + iqs[i]); } ows[i] = Double.NaN; } else { ows[i] = Linear.weight(kmWeight, wt, wo); } } else { if (errors != null) { // TODO: I18N errors.addProblem(km, "cannot find q = " + iqs[i]); } ows[i] = Double.NaN; } } } public SplineFunction createSpline( WstValueTable table, Calculation errors ) { int W = ws.length; if (W < 1) { if (errors != null) { // TODO: I18N errors.addProblem(km, "no ws found"); } return null; } double [] splineQs = new double[W]; for (int i = 0; i < W; ++i) { double sq = table.getQIndex(i, km); if (Double.isNaN(sq) && errors != null) { // TODO: I18N errors.addProblem( km, "no q found in " + (i+1) + " column"); } splineQs[i] = sq; } try { SplineInterpolator interpolator = new SplineInterpolator(); PolynomialSplineFunction spline = interpolator.interpolate(splineQs, ws); return new SplineFunction(spline, splineQs, ws); } catch (MathIllegalArgumentException miae) { if (errors != null) { // TODO: I18N errors.addProblem(km, "spline creation failed"); } log.error("spline creation failed", miae); } return null; } public SplineFunction createSpline( Row other, double km, WstValueTable table, Calculation errors ) { int W = Math.min(ws.length, other.ws.length); if (W < 1) { if (errors != null) { // TODO: I18N errors.addProblem("no ws found"); } return null; } double factor = Linear.factor(km, this.km, other.km); double [] splineQs = new double[W]; double [] splineWs = new double[W]; for (int i = 0; i < W; ++i) { double wws = Linear.weight(factor, ws[i], other.ws[i]); double wqs = Linear.weight( factor, table.getQIndex(i, km), table.getQIndex(i, other.km)); if (Double.isNaN(wws) || Double.isNaN(wqs)) { if (errors != null) { // TODO: I18N errors.addProblem(km, "cannot find w or q"); } } splineWs[i] = wws; splineQs[i] = wqs; } SplineInterpolator interpolator = new SplineInterpolator(); try { PolynomialSplineFunction spline = interpolator.interpolate(splineQs, splineWs); return new SplineFunction(spline, splineQs, splineWs); } catch (MathIllegalArgumentException miae) { if (errors != null) { // TODO: I18N errors.addProblem(km, "spline creation failed"); } log.error("spline creation failed", miae); } return null; } public double [][] interpolateWQ( Row other, double km, int steps, WstValueTable table, Calculation errors ) { SplineFunction sf = createSpline(other, km, table, errors); return sf != null ? sf.sample(steps, km, errors) : new double[2][0]; } public double [][] interpolateWQ( int steps, WstValueTable table, Calculation errors ) { SplineFunction sf = createSpline(table, errors); return sf != null ? sf.sample(steps, km, errors) : new double[2][0]; } public double getW(QPosition qPosition) { int index = qPosition.index; double weight = qPosition.weight; return weight == 1.0 ? ws[index] : Linear.weight(weight, ws[index-1], ws[index]); } public double getW( Row other, double km, QPosition qPosition ) { double kmWeight = Linear.factor(km, this.km, other.km); int index = qPosition.index; double weight = qPosition.weight; double tw, ow; if (weight == 1.0) { tw = ws[index]; ow = other.ws[index]; } else { tw = Linear.weight(weight, ws[index-1], ws[index]); ow = Linear.weight(weight, other.ws[index-1], other.ws[index]); } return Linear.weight(kmWeight, tw, ow); } public double [] findQsForW(double w, WstValueTable table) { TDoubleArrayList qs = new TDoubleArrayList(); if (ws.length > 0 && Math.abs(ws[0]-w) < 0.000001) { double q = table.getQIndex(0, km); if (!Double.isNaN(q)) { qs.add(q); } } for (int i = 1; i < ws.length; ++i) { double w2 = ws[i]; if (Double.isNaN(w2)) { continue; } if (Math.abs(w2-w) < 0.000001) { double q = table.getQIndex(i, km); if (!Double.isNaN(q)) { qs.add(q); } continue; } double w1 = ws[i-1]; if (Double.isNaN(w1)) { continue; } if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) { continue; } double q1 = table.getQIndex(i-1, km); double q2 = table.getQIndex(i, km); if (Double.isNaN(q1) || Double.isNaN(q2)) { continue; } double q = Linear.linear(w, w1, w2, q1, q2); qs.add(q); } return qs.toNativeArray(); } public double [] findQsForW( Row other, double w, double km, WstValueTable table ) { TDoubleArrayList qs = new TDoubleArrayList(); double factor = Linear.factor(km, this.km, other.km); if (ws.length > 0) { double wt = Linear.weight(factor, ws[0], other.ws[0]); if (!Double.isNaN(wt)) { double q = table.getQIndex(0, km); if (!Double.isNaN(q)) { qs.add(q); } } } for (int i = 1; i < ws.length; ++i) { double w2 = Linear.weight(factor, ws[i], other.ws[i]); if (Double.isNaN(w2)) { continue; } if (Math.abs(w2-w) < 0.000001) { double q = table.getQIndex(i, km); if (!Double.isNaN(q)) { qs.add(q); } continue; } double w1 = Linear.weight(factor, ws[i-1], other.ws[i-1]); if (Double.isNaN(w1)) { continue; } if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) { continue; } double q1 = table.getQIndex(i-1, km); double q2 = table.getQIndex(i, km); if (Double.isNaN(q1) || Double.isNaN(q2)) { continue; } double q = Linear.linear(w, w1, w2, q1, q2); qs.add(q); } return qs.toNativeArray(); } } // class Row /** Rows in table. */ protected List<Row> rows; /** Columns in table. */ protected Column [] columns; public WstValueTable() { rows = new ArrayList<Row>(); } public WstValueTable(Column [] columns) { this(); this.columns = columns; } public WstValueTable(Column [] columns, List<Row> rows) { this.columns = columns; this.rows = rows; } /** Sort rows (by km). */ public void sortRows() { Collections.sort(rows); } /** * @param km Given kilometer. * @param qs Given Q values. * @param ws output parameter. */ public double [] interpolateW(double km, double [] qs, double [] ws) { return interpolateW(km, qs, ws, null); } /** * @param ws (output parameter), gets returned. * @return output parameter ws. */ public double [] interpolateW( double km, double [] qs, double [] ws, Calculation errors ) { int rowIndex = Collections.binarySearch(rows, new Row(km)); QPosition qPosition = new QPosition(); if (rowIndex >= 0) { // direct row match Row row = rows.get(rowIndex); for (int i = 0; i < qs.length; ++i) { if (getQPosition(km, qs[i], qPosition) == null) { if (errors != null) { // TODO: I18N errors.addProblem(km, "cannot find q = " + qs[i]); } ws[i] = Double.NaN; } else { if (Double.isNaN(ws[i] = row.getW(qPosition)) && errors != null) { // TODO: I18N errors.addProblem( km, "cannot find w for q = " + qs[i]); } } } } else { // needs bilinear interpolation rowIndex = -rowIndex -1; if (rowIndex < 1 || rowIndex >= rows.size()) { // do not extrapolate Arrays.fill(ws, Double.NaN); if (errors != null) { // TODO: I18N errors.addProblem(km, "km not found"); } } else { Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); r1.interpolateW(r2, km, qs, ws, this, errors); } } return ws; } /** * Interpolate W and Q values at a given km. */ public double [][] interpolateWQ(double km) { return interpolateWQ(km, null); } /** * Interpolate W and Q values at a given km. */ public double [][] interpolateWQ(double km, Calculation errors) { return interpolateWQ(km, DEFAULT_Q_STEPS, errors); } /** * Interpolate W and Q values at a given km. */ public double [][] interpolateWQ(double km, int steps, Calculation errors) { int rowIndex = Collections.binarySearch(rows, new Row(km)); if (rowIndex >= 0) { // direct row match Row row = rows.get(rowIndex); return row.interpolateWQ(steps, this, errors); } rowIndex = -rowIndex -1; if (rowIndex < 1 || rowIndex >= rows.size()) { // do not extrapolate if (errors != null) { // TODO: I18N errors.addProblem(km, "km not found"); } return new double[2][0]; } Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); return r1.interpolateWQ(r2, km, steps, this, errors); } public boolean interpolate( double km, double [] out, QPosition qPosition, Function qFunction ) { int R1 = rows.size()-1; out[1] = qFunction.value(getQ(qPosition, km)); if (Double.isNaN(out[1])) { return false; } QPosition nPosition = new QPosition(); if (getQPosition(km, out[1], nPosition) == null) { return false; } int rowIndex = Collections.binarySearch(rows, new Row(km)); if (rowIndex >= 0) { // direct row match out[0] = rows.get(rowIndex).getW(nPosition); return !Double.isNaN(out[0]); } rowIndex = -rowIndex -1; if (rowIndex < 1 || rowIndex > R1) { // do not extrapolate return false; } Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); out[0] = r1.getW(r2, km, nPosition); return !Double.isNaN(out[0]); } /** * Look up interpolation of a Q at given positions. * * @param q the non-interpolated Q value. * @param referenceKm the reference km (e.g. gauge position). * @param kms positions for which to interpolate. * @param ws (output) resulting interpolated ws. * @param qs (output) resulting interpolated qs. * @param errors calculation object to store errors. */ public QPosition interpolate( double q, double referenceKm, double [] kms, double [] ws, double [] qs, Calculation errors ) { return interpolate( q, referenceKm, kms, ws, qs, 0, kms.length, errors); } public QPosition interpolate( double q, double referenceKm, double [] kms, double [] ws, double [] qs, int startIndex, int length, Calculation errors ) { QPosition qPosition = getQPosition(referenceKm, q); if (qPosition == null) { // we cannot locate q at km Arrays.fill(ws, Double.NaN); Arrays.fill(qs, Double.NaN); if (errors != null) { errors.addProblem(referenceKm, "cannot find q"); } return null; } Row kmKey = new Row(); int R1 = rows.size()-1; for (int i = startIndex, end = startIndex+length; i < end; ++i) { if (Double.isNaN(qs[i] = getQ(qPosition, kms[i]))) { if (errors != null) { errors.addProblem(kms[i], "cannot find q"); } ws[i] = Double.NaN; continue; } kmKey.km = kms[i]; int rowIndex = Collections.binarySearch(rows, kmKey); if (rowIndex >= 0) { // direct row match if (Double.isNaN(ws[i] = rows.get(rowIndex).getW(qPosition)) && errors != null) { errors.addProblem(kms[i], "cannot find w"); } continue; } rowIndex = -rowIndex -1; if (rowIndex < 1 || rowIndex > R1) { // do not extrapolate if (errors != null) { errors.addProblem(kms[i], "cannot find km"); } ws[i] = Double.NaN; continue; } Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); if (Double.isNaN(ws[i] = r1.getW(r2, kms[i], qPosition)) && errors != null) { errors.addProblem(kms[i], "cannot find w"); } } return qPosition; } /** * Linearly interpolate w at a km at a column of two rows. * * @param km position for which to interpolate. * @param row1 first row. * @param row2 second row. * @param col column-index at which to look. * * @return Linearly interpolated w, NaN if one of the given rows was null. */ public static double linearW(double km, Row row1, Row row2, int col) { if (row1 == null || row2 == null) { return Double.NaN; } return Linear.linear(km, row1.km, row2.km, row1.ws[col], row2.ws[col]); } /** * Do interpolation/lookup of W and Q within columns (i.e. ignoring values * of other columns). * @param km position (km) at which to interpolate/lookup. * @return [[q0, q1, .. qx] , [w0, w1, .. wx]] (can contain NaNs) */ public double [][] interpolateWQColumnwise(double km) { log.debug("WstValueTable.interpolateWQColumnwise"); double [] qs = new double[columns.length]; double [] ws = new double[columns.length]; // Find out row from where we will start searching. int rowIndex = Collections.binarySearch(rows, new Row(km)); if (rowIndex < 0) { rowIndex = -rowIndex -1; } if (rowIndex >= rows.size()) { rowIndex = rows.size() -1; } Row startRow = rows.get(rowIndex); for (int col = 0; col < columns.length; col++) { qs[col] = columns[col].getQRangeTree().findQ(km); if (startRow.km == km && startRow.ws[col] != Double.NaN) { // Great. W is defined at km. ws[col] = startRow.ws[col]; continue; } // Search neighbouring rows that define w at this col. Row rowBefore = null; Row rowAfter = null; for (int before = rowIndex -1; before >= 0; before--) { if (!Double.isNaN(rows.get(before).ws[col])) { rowBefore = rows.get(before); break; } } for (int after = rowIndex, R = rows.size(); after < R; after++) { if (!Double.isNaN(rows.get(after).ws[col])) { rowAfter = rows.get(after); break; } } ws[col] = linearW(km, rowBefore, rowAfter, col); } return new double [][] {qs, ws}; } public double [] findQsForW(double km, double w) { int rowIndex = Collections.binarySearch(rows, new Row(km)); if (rowIndex >= 0) { return rows.get(rowIndex).findQsForW(w, this); } rowIndex = -rowIndex - 1; if (rowIndex < 1 || rowIndex >= rows.size()) { // Do not extrapolate. return new double[0]; } // Needs bilinear interpolation. Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); return r1.findQsForW(r2, w, km, this); } protected SplineFunction createSpline(double km, Calculation errors) { int rowIndex = Collections.binarySearch(rows, new Row(km)); if (rowIndex >= 0) { SplineFunction sf = rows.get(rowIndex).createSpline(this, errors); if (sf == null && errors != null) { // TODO: I18N errors.addProblem(km, "cannot create w/q relation"); } return sf; } rowIndex = -rowIndex - 1; if (rowIndex < 1 || rowIndex >= rows.size()) { // Do not extrapolate. if (errors != null) { // TODO: I18N errors.addProblem(km, "km not found"); } return null; } // Needs bilinear interpolation. Row r1 = rows.get(rowIndex-1); Row r2 = rows.get(rowIndex); SplineFunction sf = r1.createSpline(r2, km, this, errors); if (sf == null && errors != null) { // TODO: I18N errors.addProblem(km, "cannot create w/q relation"); } return sf; } /** 'Bezugslinienverfahren' */ public double [][] relateWs( double km1, double km2, int numSamples, Calculation errors ) { SplineFunction sf1 = createSpline(km1, errors); if (sf1 == null) { return new double[2][0]; } SplineFunction sf2 = createSpline(km1, errors); if (sf2 == null) { return new double[2][0]; } PolynomialSplineFunction iQ1 = sf1.createIndexQRelation(); if (iQ1 == null) { if (errors != null) { // TODO: I18N errors.addProblem(km1, "cannot create index/q relation"); } return new double[2][0]; } PolynomialSplineFunction iQ2 = sf1.createIndexQRelation(); if (iQ2 == null) { if (errors != null) { // TODO: I18N errors.addProblem(km2, "cannot create index/q relation"); } return new double[2][0]; } int N = sf1.splineQs.length; double stepWidth = N/(double)numSamples; PolynomialSplineFunction qW1 = sf1.spline; PolynomialSplineFunction qW2 = sf2.spline; double [] ws1 = new double[numSamples]; double [] ws2 = new double[numSamples]; Arrays.fill(ws1, Double.NaN); Arrays.fill(ws2, Double.NaN); boolean hadErrors = false; double p = 0d; for (int i = 0; i < numSamples; ++i, p += stepWidth) { try { double q1 = iQ1.value(p); double w1 = qW1.value(q1); double q2 = iQ2.value(p); double w2 = qW2.value(q2); ws1[i] = w1; ws2[i] = w2; } catch (ArgumentOutsideDomainException aode) { if (!hadErrors) { // XXX: I'm not sure if this really can happen // and if we should report this more than once. hadErrors = true; if (errors != null) { // TODO: I18N log.debug("W~W failed", aode); errors.addProblem("W~W failed"); } } } } return new double [][] { ws1, ws2 }; } public QPosition getQPosition(double km, double q) { return getQPosition(km, q, new QPosition()); } public QPosition getQPosition(double km, double q, QPosition qPosition) { if (columns.length == 0) { return null; } double qLast = columns[0].getQRangeTree().findQ(km); if (Math.abs(qLast - q) < 0.00001) { return qPosition.set(0, 1d); } for (int i = 1; i < columns.length; ++i) { double qCurrent = columns[i].getQRangeTree().findQ(km); if (Math.abs(qCurrent - q) < 0.00001) { return qPosition.set(i, 1d); } double qMin, qMax; if (qLast < qCurrent) { qMin = qLast; qMax = qCurrent; } else { qMin = qCurrent; qMax = qLast; } if (q > qMin && q < qMax) { double weight = Linear.factor(q, qLast, qCurrent); return qPosition.set(i, weight); } qLast = qCurrent; } return null; } public double getQIndex(int index, double km) { return columns[index].getQRangeTree().findQ(km); } public double getQ(QPosition qPosition, double km) { int index = qPosition.index; double weight = qPosition.weight; if (weight == 1d) { return columns[index].getQRangeTree().findQ(km); } double q1 = columns[index-1].getQRangeTree().findQ(km); double q2 = columns[index ].getQRangeTree().findQ(km); return Linear.weight(weight, q1, q2); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :