Mercurial > dive4elements > river
annotate artifacts/src/main/java/org/dive4elements/river/artifacts/sinfo/flowdepth/BedQualityD50KmValueFinder.java @ 8911:37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
author | mschaefer |
---|---|
date | Fri, 23 Feb 2018 09:30:24 +0100 |
parents | 0a900d605d52 |
children | 86395ab8ebc3 |
rev | line source |
---|---|
8898 | 1 /* Copyright (C) 2017 by Bundesanstalt für Gewässerkunde |
2 * Software engineering by | |
3 * Björnsen Beratende Ingenieure GmbH | |
4 * Dr. Schumacher Ingenieurbüro für Wasser und Umwelt | |
5 * | |
6 * This file is Free Software under the GNU AGPL (>=v3) | |
7 * and comes with ABSOLUTELY NO WARRANTY! Check out the | |
8 * documentation coming with Dive4Elements River for details. | |
9 */ | |
10 | |
11 package org.dive4elements.river.artifacts.sinfo.flowdepth; | |
12 | |
13 import java.util.Date; | |
14 import java.util.List; | |
15 | |
16 import org.apache.commons.lang.math.DoubleRange; | |
17 import org.apache.commons.math.ArgumentOutsideDomainException; | |
18 import org.apache.commons.math.analysis.interpolation.LinearInterpolator; | |
19 import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction; | |
20 import org.apache.log4j.Logger; | |
8901 | 21 import org.dive4elements.river.artifacts.math.Utils; |
8898 | 22 import org.dive4elements.river.artifacts.model.DateRange; |
23 import org.dive4elements.river.backend.SedDBSessionHolder; | |
24 import org.dive4elements.river.model.River; | |
8901 | 25 import org.dive4elements.river.utils.DoubleUtil; |
8898 | 26 import org.hibernate.SQLQuery; |
27 import org.hibernate.Session; | |
28 import org.hibernate.type.StandardBasicTypes; | |
29 | |
8901 | 30 import gnu.trove.TDoubleArrayList; |
31 | |
8898 | 32 /** |
8901 | 33 * Searchable sorted km array with parallel bed measurements value array and linear interpolation for km and d50 between the array elements.<br /> |
34 * <br /> | |
35 * See comment of SQL command on how the values are filtered and aggregated. | |
36 * | |
8898 | 37 * @author Matthias Schäfer |
38 * | |
39 */ | |
40 public class BedQualityD50KmValueFinder { | |
41 | |
42 /***** INNER CLASSES *****/ | |
43 | |
44 /** | |
45 * A bed measurements aggregate with its d50 characteristic grain diameter | |
46 */ | |
47 private class D50Measurement { | |
48 private double km; | |
49 public double getKm() { | |
50 return km; | |
51 } | |
52 private Date mindate; | |
53 public Date getMinDate() { | |
54 return mindate; | |
55 } | |
56 private Date maxdate; | |
57 public Date getMaxDate() { | |
58 return maxdate; | |
59 } | |
60 private int cnt; | |
61 public int getCnt() { | |
62 return cnt; | |
63 } | |
64 private double mindepth; | |
65 public double getMinDepth() { | |
66 return mindepth; | |
67 } | |
68 private double maxdepth; | |
69 public double getMaxDepth() { | |
70 return maxdepth; | |
71 } | |
72 private double d50; | |
73 /** | |
74 * D50 in m | |
75 */ | |
76 public double getD50() { | |
77 return d50; | |
78 } | |
79 /** | |
80 * Parameter constructor | |
81 */ | |
82 public D50Measurement(double km, Date mindate, Date maxdate, int cnt, double mindepth, double maxdepth, double d50mm) { | |
83 this.km = km; | |
84 this.mindate = mindate; | |
85 this.maxdate = maxdate; | |
86 this.cnt = cnt; | |
87 this.mindepth = mindepth; | |
88 this.maxdepth = maxdepth; | |
89 this.d50 = d50mm / 1000; | |
90 } | |
91 | |
92 /** | |
93 * Query result row constructor | |
94 */ | |
95 public D50Measurement(Object[] tuple, String[] aliases) { | |
96 km = 0; | |
97 mindate = null; | |
98 maxdate = null; | |
99 cnt = 0; | |
100 mindepth = Double.NaN; | |
101 maxdepth = Double.NaN; | |
102 d50 = Double.NaN; | |
103 for (int i = 0; i < tuple.length; ++i) { | |
104 if (tuple[i] == null) | |
105 continue; | |
106 switch (aliases[i]) { | |
107 case "km": | |
108 km = ((Number) tuple[i]).doubleValue(); | |
109 break; | |
110 case "mindate": | |
111 mindate = (Date) tuple[i]; | |
112 break; | |
113 case "maxdate": | |
114 maxdate = (Date) tuple[i]; | |
115 break; | |
116 case "cnt": | |
117 cnt = ((Number) tuple[i]).intValue(); | |
118 break; | |
119 case "mindepth": | |
120 mindepth = ((Number) tuple[i]).doubleValue(); | |
121 break; | |
122 case "maxdepth": | |
123 maxdepth = ((Number) tuple[i]).doubleValue(); | |
124 break; | |
125 case "d50": | |
126 d50 = ((Number) tuple[i]).doubleValue() / 1000; // mm to m | |
127 break; | |
128 default: | |
129 break; | |
130 } | |
131 } | |
132 } | |
133 } | |
134 | |
135 /***** FIELDS *****/ | |
136 | |
137 /** | |
138 * Private log to use here. | |
139 */ | |
140 private static Logger log = Logger.getLogger(BedQualityD50KmValueFinder.class); | |
141 | |
142 /** | |
143 * Query that aggregates by km for a km range and a time period all sub layer bed measurements with their d50<br /> | |
144 * <br /> | |
145 * A km may have bed measurements for multiple dates, multiple distances from the river bank, and multiple depth layers. | |
146 * The query filters by km range, time period and layer (sub layer: below bed to max. 50 cm depth). | |
147 * Those measurements are then grouped by km, and the D50 aggregated as average value. | |
148 */ | |
149 private static final String SQL_BED_D50_SUBLAYER_MEASUREMENT = | |
150 "SELECT t.km, MIN(t.datum) AS mindate, MAX(t.datum) AS maxdate, COUNT(*) AS cnt," | |
151 + " MIN(p.tiefevon) AS mindepth, MAX(p.tiefebis) AS maxdepth, AVG(a.d50) AS d50" | |
152 + " FROM sohltest t INNER JOIN station s ON t.stationid = s.stationid" | |
153 + " INNER JOIN gewaesser g ON s.gewaesserid = g.gewaesserid" | |
154 + " INNER JOIN sohlprobe p ON t.sohltestid = p.sohltestid" | |
155 + " INNER JOIN siebanalyse a ON p.sohlprobeid = a.sohlprobeid" | |
156 + " WHERE (g.name = :name) AND (s.km BETWEEN :fromkm - 0.0001 AND :tokm + 0.0001)" | |
157 + " AND (p.tiefevon > 0.0) AND (p.tiefebis <= 0.5)" | |
158 + " AND (t.datum BETWEEN :fromdate AND :todate)" | |
159 + " GROUP BY t.km" | |
160 + " ORDER BY t.km"; | |
161 private static final String[] SQL_BED_D50_SELECT_ALIAS = {"km", "mindate", "maxdate", "cnt", "mindepth", "maxdepth", "d50"}; | |
162 | |
163 /** | |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
164 * Real linear interpolator for kms and d50 values |
8898 | 165 */ |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
166 private PolynomialSplineFunction interpolator; |
8901 | 167 |
8898 | 168 |
169 /***** METHODS *****/ | |
170 | |
171 /** | |
172 * Returns the d50 value interpolated according to a km | |
8901 | 173 * @return d50 (mm) of the km, or NaN |
8898 | 174 */ |
175 public double findD50(double km) throws ArgumentOutsideDomainException { | |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
176 return interpolator.value(km); |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
177 /* ohne interpolation: |
8901 | 178 if ((kms == null) || (kms.size() == 0)) |
179 return Double.NaN; | |
180 int i = kms.binarySearch(km); | |
181 if (i >= 0) | |
182 return values.get(i); | |
183 i = -i - 1; | |
184 if ((i - 1 >= 0) && Utils.epsilonEquals(km, kms.get(i - 1), 0.0001)) | |
185 return values.get(i - 1); | |
186 else if ((i >= 0) && (i <= kms.size() - 1) && Utils.epsilonEquals(km, kms.get(i), 0.0001)) | |
187 return values.get(i); | |
188 else | |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
189 return Double.NaN; */ |
8898 | 190 } |
191 | |
192 /** | |
193 * Loads the range of the river's kms with their associated values. | |
194 * @return Whether the load has been successful | |
195 */ | |
196 public boolean loadValues(final River river, final DoubleRange kmRange, final DateRange dateRange) { | |
197 log.debug(String.format("loadValues km %.3f - %.3f %tF - %tF", kmRange.getMinimumDouble(), kmRange.getMaximumDouble(), dateRange.getFrom(), dateRange.getTo())); | |
198 Session session = SedDBSessionHolder.HOLDER.get(); | |
199 SQLQuery sqlQuery = session.createSQLQuery(SQL_BED_D50_SUBLAYER_MEASUREMENT) | |
200 .addScalar("km", StandardBasicTypes.DOUBLE) | |
201 .addScalar("mindate", StandardBasicTypes.DATE) | |
202 .addScalar("maxdate", StandardBasicTypes.DATE) | |
203 .addScalar("cnt", StandardBasicTypes.INTEGER) | |
204 .addScalar("mindepth", StandardBasicTypes.DOUBLE) | |
205 .addScalar("maxdepth", StandardBasicTypes.DOUBLE) | |
206 .addScalar("d50", StandardBasicTypes.DOUBLE); | |
207 String seddbRiver = river.nameForSeddb(); | |
208 sqlQuery.setString("name", seddbRiver); | |
209 sqlQuery.setDouble("fromkm", kmRange.getMinimumDouble()); | |
210 sqlQuery.setDouble("tokm", kmRange.getMaximumDouble()); | |
211 sqlQuery.setDate("fromdate", dateRange.getFrom()); | |
212 sqlQuery.setDate("todate", dateRange.getTo()); | |
213 @SuppressWarnings("unchecked") | |
214 final List<Object[]> rows = sqlQuery.list(); | |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
215 final double[] kms = new double[rows.size()]; |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
216 final double[] values = new double[rows.size()]; |
8898 | 217 D50Measurement measurement; |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
218 int i = -1; |
8898 | 219 for (Object[] row : rows) { |
220 measurement = new D50Measurement(row, SQL_BED_D50_SELECT_ALIAS); | |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
221 i++; |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
222 kms[i] = measurement.getKm(); |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
223 values[i] = measurement.getD50(); |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
224 log.debug(String.format("loadValues km %.3f d50(mm) %.1f count %d", kms[i], values[i], measurement.getCnt())); |
8898 | 225 } |
8911
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
226 try { |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
227 interpolator = new LinearInterpolator().interpolate(kms, values); |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
228 return true; |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
229 } catch (Exception e) { |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
230 interpolator = null; |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
231 return false; |
37ff7f435912
SINFO Flowdepth: more error checks, d50 interpolation, avoid negative tkh
mschaefer
parents:
8901
diff
changeset
|
232 } |
8898 | 233 } |
234 | |
235 } |