comparison artifacts/src/main/java/org/dive4elements/river/artifacts/model/WstValueTable.java @ 9286:bcbae86ce7b3

Improved flood duration curve calculation as specified by BfG
author mschaefer
date Mon, 23 Jul 2018 19:20:06 +0200
parents 5e38e2924c07
children 853f2dafc16e
comparison
equal deleted inserted replaced
9285:9b16f58c62a7 9286:bcbae86ce7b3
6 * documentation coming with Dive4Elements River for details. 6 * documentation coming with Dive4Elements River for details.
7 */ 7 */
8 8
9 package org.dive4elements.river.artifacts.model; 9 package org.dive4elements.river.artifacts.model;
10 10
11 import static org.dive4elements.river.backend.utils.EpsilonComparator.CMP;
12
11 import java.io.Serializable; 13 import java.io.Serializable;
12 14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collections;
17 import java.util.List;
18
19 import org.apache.commons.math.ArgumentOutsideDomainException;
20 import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
21 import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
22 import org.apache.commons.math.exception.MathIllegalArgumentException;
23 import org.apache.log4j.Logger;
24 import org.dive4elements.river.artifacts.math.Function;
13 import org.dive4elements.river.artifacts.math.Linear; 25 import org.dive4elements.river.artifacts.math.Linear;
14 import org.dive4elements.river.artifacts.math.Function;
15 import org.dive4elements.river.utils.DoubleUtil; 26 import org.dive4elements.river.utils.DoubleUtil;
16 27
17 import java.util.Arrays;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Collections;
21
22 import org.apache.log4j.Logger;
23
24 import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
25
26 import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
27
28 import org.apache.commons.math.ArgumentOutsideDomainException;
29
30 import org.apache.commons.math.exception.MathIllegalArgumentException;
31
32 import gnu.trove.TDoubleArrayList; 28 import gnu.trove.TDoubleArrayList;
33
34 import static org.dive4elements.river.backend.utils.EpsilonComparator.CMP;
35 29
36 /** 30 /**
37 * W, Q and km data from database 'wsts' spiced with interpolation algorithms. 31 * W, Q and km data from database 'wsts' spiced with interpolation algorithms.
38 */ 32 */
39 public class WstValueTable 33 public class WstValueTable
58 protected QRangeTree qRangeTree; 52 protected QRangeTree qRangeTree;
59 53
60 public Column() { 54 public Column() {
61 } 55 }
62 56
63 public Column(String name) { 57 public Column(final String name) {
64 this.name = name; 58 this.name = name;
65 } 59 }
66 60
67 public String getName() { 61 public String getName() {
68 return name; 62 return this.name;
69 } 63 }
70 64
71 public void setName(String name) { 65 public void setName(final String name) {
72 this.name = name; 66 this.name = name;
73 } 67 }
74 68
75 public QRangeTree getQRangeTree() { 69 public QRangeTree getQRangeTree() {
76 return qRangeTree; 70 return this.qRangeTree;
77 } 71 }
78 72
79 public void setQRangeTree(QRangeTree qRangeTree) { 73 public void setQRangeTree(final QRangeTree qRangeTree) {
80 this.qRangeTree = qRangeTree; 74 this.qRangeTree = qRangeTree;
81 } 75 }
82 } // class Column 76 } // class Column
83 77
84 /** 78 /**
90 protected double weight; 84 protected double weight;
91 85
92 public QPosition() { 86 public QPosition() {
93 } 87 }
94 88
95 public QPosition(int index, double weight) { 89 public QPosition(final int index, final double weight) {
96 this.index = index; 90 this.index = index;
97 this.weight = weight; 91 this.weight = weight;
98 } 92 }
99 93
100 public QPosition set(int index, double weight) { 94 public QPosition set(final int index, final double weight) {
101 this.index = index; 95 this.index = index;
102 this.weight = weight; 96 this.weight = weight;
103 return this; 97 return this;
104 } 98 }
105 99
110 public PolynomialSplineFunction spline; 104 public PolynomialSplineFunction spline;
111 public double [] splineQs; 105 public double [] splineQs;
112 public double [] splineWs; 106 public double [] splineWs;
113 107
114 public SplineFunction( 108 public SplineFunction(
115 PolynomialSplineFunction spline, 109 final PolynomialSplineFunction spline,
116 double [] splineQs, 110 final double [] splineQs,
117 double [] splineWs 111 final double [] splineWs
118 ) { 112 ) {
119 this.spline = spline; 113 this.spline = spline;
120 this.splineQs = splineQs; 114 this.splineQs = splineQs;
121 this.splineWs = splineWs; 115 this.splineWs = splineWs;
122 } 116 }
123 117
124 public double [][] sample( 118 public double [][] sample(
125 int numSamples, 119 final int numSamples,
126 double km, 120 final double km,
127 Calculation errors 121 final Calculation errors
128 ) { 122 ) {
129 double minQ = getQMin(); 123 final double minQ = getQMin();
130 double maxQ = getQMax(); 124 final double maxQ = getQMax();
131 125
132 double [] outWs = new double[numSamples]; 126 final double [] outWs = new double[numSamples];
133 double [] outQs = new double[numSamples]; 127 final double [] outQs = new double[numSamples];
134 128
135 Arrays.fill(outWs, Double.NaN); 129 Arrays.fill(outWs, Double.NaN);
136 Arrays.fill(outQs, Double.NaN); 130 Arrays.fill(outQs, Double.NaN);
137 131
138 double stepWidth = (maxQ - minQ)/numSamples; 132 final double stepWidth = (maxQ - minQ)/numSamples;
139 133
140 try { 134 try {
141 double q = minQ; 135 double q = minQ;
142 for (int i = 0; i < outWs.length; ++i, q += stepWidth) { 136 for (int i = 0; i < outWs.length; ++i, q += stepWidth) {
143 outWs[i] = spline.value(outQs[i] = q); 137 outWs[i] = this.spline.value(outQs[i] = q);
144 } 138 }
145 } 139 }
146 catch (ArgumentOutsideDomainException aode) { 140 catch (final ArgumentOutsideDomainException aode) {
147 if (errors != null) { 141 if (errors != null) {
148 errors.addProblem(km, "spline.interpolation.failed"); 142 errors.addProblem(km, "spline.interpolation.failed");
149 } 143 }
150 log.error("spline interpolation failed.", aode); 144 log.error("spline interpolation failed.", aode);
151 } 145 }
152 146
153 return new double [][] { outWs, outQs }; 147 return new double [][] { outWs, outQs };
154 } 148 }
155 149
156 public double getQMin() { 150 public double getQMin() {
157 return Math.min(splineQs[0], splineQs[splineQs.length-1]); 151 return Math.min(this.splineQs[0], this.splineQs[this.splineQs.length-1]);
158 } 152 }
159 153
160 public double getQMax() { 154 public double getQMax() {
161 return Math.max(splineQs[0], splineQs[splineQs.length-1]); 155 return Math.max(this.splineQs[0], this.splineQs[this.splineQs.length-1]);
162 } 156 }
163 157
164 /** Constructs a continues index between the columns to Qs. */ 158 /** Constructs a continues index between the columns to Qs. */
165 public PolynomialSplineFunction createIndexQRelation() { 159 public PolynomialSplineFunction createIndexQRelation() {
166 160
167 double [] indices = new double[splineQs.length]; 161 final double [] indices = new double[this.splineQs.length];
168 for (int i = 0; i < indices.length; ++i) { 162 for (int i = 0; i < indices.length; ++i) {
169 indices[i] = i; 163 indices[i] = i;
170 } 164 }
171 165
172 try { 166 try {
173 SplineInterpolator interpolator = new SplineInterpolator(); 167 final SplineInterpolator interpolator = new SplineInterpolator();
174 return interpolator.interpolate(indices, splineQs); 168 return interpolator.interpolate(indices, this.splineQs);
175 } 169 }
176 catch (MathIllegalArgumentException miae) { 170 catch (final MathIllegalArgumentException miae) {
177 // Ignore me! 171 // Ignore me!
178 } 172 }
179 return null; 173 return null;
180 } 174 }
181 } // class SplineFunction 175 } // class SplineFunction
190 double [] ws; 184 double [] ws;
191 185
192 public Row() { 186 public Row() {
193 } 187 }
194 188
195 public Row(double km) { 189 public Row(final double km) {
196 this.km = km; 190 this.km = km;
197 } 191 }
198 192
199 public Row(double km, double [] ws) { 193 public Row(final double km, final double [] ws) {
200 this(km); 194 this(km);
201 this.ws = ws; 195 this.ws = ws;
202 } 196 }
203 197
204 /** 198 /**
205 * Sort Qs and Ws for this Row over Q. 199 * Sort Qs and Ws for this Row over Q.
206 */ 200 */
207 private double[][] getSortedWQ( 201 private double[][] getSortedWQ(
208 WstValueTable table, 202 final WstValueTable table,
209 Calculation errors 203 final Calculation errors
210 ) { 204 ) {
211 int W = ws.length; 205 final int W = this.ws.length;
212 206
213 if (W < 1) { 207 if (W < 1) {
214 if (errors != null) { 208 if (errors != null) {
215 errors.addProblem(km, "no.ws.found"); 209 errors.addProblem(this.km, "no.ws.found");
216 } 210 }
217 return new double[][] {{Double.NaN}, {Double.NaN}}; 211 return new double[][] {{Double.NaN}, {Double.NaN}};
218 } 212 }
219 213
220 double [] sortedWs = ws; 214 final double [] sortedWs = this.ws;
221 double [] sortedQs = new double[W]; 215 final double [] sortedQs = new double[W];
222 216
223 for (int i=0; i < W; ++i) { 217 for (int i=0; i < W; ++i) {
224 double q = table.getQIndex(i, km); 218 final double q = table.getQIndex(i, this.km);
225 if (Double.isNaN(q) && errors != null) { 219 if (Double.isNaN(q) && errors != null) {
226 errors.addProblem( 220 errors.addProblem(
227 km, "no.q.found.in.column", i+1); 221 this.km, "no.q.found.in.column", i+1);
228 } 222 }
229 sortedQs[i] = q; 223 sortedQs[i] = q;
230 } 224 }
231 225
232 DoubleUtil.sortByFirst(sortedQs, sortedWs); 226 DoubleUtil.sortByFirst(sortedQs, sortedWs);
237 /** 231 /**
238 * Return an array of Qs and Ws for the given km between 232 * Return an array of Qs and Ws for the given km between
239 * this Row and another, sorted over Q. 233 * this Row and another, sorted over Q.
240 */ 234 */
241 private double[][] getSortedWQ( 235 private double[][] getSortedWQ(
242 Row other, 236 final Row other,
243 double km, 237 final double km,
244 WstValueTable table, 238 final WstValueTable table,
245 Calculation errors 239 final Calculation errors
246 ) { 240 ) {
247 int W = Math.min(ws.length, other.ws.length); 241 final int W = Math.min(this.ws.length, other.ws.length);
248 242
249 if (W < 1) { 243 if (W < 1) {
250 if (errors != null) { 244 if (errors != null) {
251 errors.addProblem("no.ws.found"); 245 errors.addProblem("no.ws.found");
252 } 246 }
253 return new double[][] {{Double.NaN}, {Double.NaN}}; 247 return new double[][] {{Double.NaN}, {Double.NaN}};
254 } 248 }
255 249
256 double factor = Linear.factor(km, this.km, other.km); 250 final double factor = Linear.factor(km, this.km, other.km);
257 251
258 double [] sortedQs = new double[W]; 252 final double [] sortedQs = new double[W];
259 double [] sortedWs = new double[W]; 253 final double [] sortedWs = new double[W];
260 254
261 for (int i = 0; i < W; ++i) { 255 for (int i = 0; i < W; ++i) {
262 double wws = Linear.weight(factor, ws[i], other.ws[i]); 256 final double wws = Linear.weight(factor, this.ws[i], other.ws[i]);
263 double wqs = table.getQIndex(i, km); 257 final double wqs = table.getQIndex(i, km);
264 258
265 if (Double.isNaN(wws) || Double.isNaN(wqs)) { 259 if (Double.isNaN(wws) || Double.isNaN(wqs)) {
266 if (errors != null) { 260 if (errors != null) {
267 errors.addProblem(km, "cannot.find.w.or.q"); 261 errors.addProblem(km, "cannot.find.w.or.q");
268 } 262 }
278 } 272 }
279 273
280 /** 274 /**
281 * Find Qs matching w in an array of Qs and Ws sorted over Q. 275 * Find Qs matching w in an array of Qs and Ws sorted over Q.
282 */ 276 */
283 private double[] findQsForW(double w, double[][] sortedWQ) { 277 private double[] findQsForW(final double w, final double[][] sortedWQ) {
284 int W = sortedWQ[0].length; 278 final int W = sortedWQ[0].length;
285 279
286 double[] sortedQs = sortedWQ[1]; 280 final double[] sortedQs = sortedWQ[1];
287 double[] sortedWs = sortedWQ[0]; 281 final double[] sortedWs = sortedWQ[0];
288 282
289 TDoubleArrayList qs = new TDoubleArrayList(); 283 final TDoubleArrayList qs = new TDoubleArrayList();
290 284
291 if (W > 0 && Math.abs(sortedWs[0]-w) < W_EPSILON) { 285 if (W > 0 && Math.abs(sortedWs[0]-w) < W_EPSILON) {
292 double q = sortedQs[0]; 286 final double q = sortedQs[0];
293 if (!Double.isNaN(q)) { 287 if (!Double.isNaN(q)) {
294 qs.add(q); 288 qs.add(q);
295 } 289 }
296 } 290 }
297 291
298 for (int i = 1; i < W; ++i) { 292 for (int i = 1; i < W; ++i) {
299 double w2 = sortedWs[i]; 293 final double w2 = sortedWs[i];
300 if (Double.isNaN(w2)) { 294 if (Double.isNaN(w2)) {
301 continue; 295 continue;
302 } 296 }
303 if (Math.abs(w2-w) < W_EPSILON) { 297 if (Math.abs(w2-w) < W_EPSILON) {
304 double q = sortedQs[i]; 298 final double q = sortedQs[i];
305 if (!Double.isNaN(q)) { 299 if (!Double.isNaN(q)) {
306 qs.add(q); 300 qs.add(q);
307 } 301 }
308 continue; 302 continue;
309 } 303 }
310 double w1 = sortedWs[i-1]; 304 final double w1 = sortedWs[i-1];
311 if (Double.isNaN(w1)) { 305 if (Double.isNaN(w1)) {
312 continue; 306 continue;
313 } 307 }
314 308
315 if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) { 309 if (w < Math.min(w1, w2) || w > Math.max(w1, w2)) {
316 continue; 310 continue;
317 } 311 }
318 312
319 double q1 = sortedQs[i-1]; 313 final double q1 = sortedQs[i-1];
320 double q2 = sortedQs[i]; 314 final double q2 = sortedQs[i];
321 if (Double.isNaN(q1) || Double.isNaN(q2)) { 315 if (Double.isNaN(q1) || Double.isNaN(q2)) {
322 continue; 316 continue;
323 } 317 }
324 318
325 double q = Linear.linear(w, w1, w2, q1, q2); 319 final double q = Linear.linear(w, w1, w2, q1, q2);
326 qs.add(q); 320 qs.add(q);
327 } 321 }
328 322
329 return qs.toNativeArray(); 323 return qs.toNativeArray();
330 } 324 }
331 325
332 /** 326 /**
333 * Compare according to place of measurement (km). 327 * Compare according to place of measurement (km).
334 */ 328 */
335 public int compareTo(Row other) { 329 @Override
336 return CMP.compare(km, other.km); 330 public int compareTo(final Row other) {
331 return CMP.compare(this.km, other.km);
337 } 332 }
338 333
339 /** 334 /**
340 * Interpolate Ws, given Qs and a km. 335 * Interpolate Ws, given Qs and a km.
341 * 336 *
342 * @param iqs Given ("input") Qs. 337 * @param iqs Given ("input") Qs.
343 * @param ows Resulting ("output") Ws. 338 * @param ows Resulting ("output") Ws.
344 * @param table Table of which to use data for interpolation. 339 * @param table Table of which to use data for interpolation.
345 */ 340 */
346 public void interpolateW( 341 public void interpolateW(
347 Row other, 342 final Row other,
348 double km, 343 final double km,
349 double [] iqs, 344 final double [] iqs,
350 double [] ows, 345 final double [] ows,
351 WstValueTable table, 346 final WstValueTable table,
352 Calculation errors 347 final Calculation errors
353 ) { 348 ) {
354 double kmWeight = Linear.factor(km, this.km, other.km); 349 final double kmWeight = Linear.factor(km, this.km, other.km);
355 350
356 QPosition qPosition = new QPosition(); 351 final QPosition qPosition = new QPosition();
357 352
358 for (int i = 0; i < iqs.length; ++i) { 353 for (int i = 0; i < iqs.length; ++i) {
359 if (table.getQPosition(km, iqs[i], qPosition) != null) { 354 if (table.getQPosition(km, iqs[i], qPosition) != null) {
360 double wt = getW(qPosition); 355 final double wt = getW(qPosition);
361 double wo = other.getW(qPosition); 356 final double wo = other.getW(qPosition);
362 if (Double.isNaN(wt) || Double.isNaN(wo)) { 357 if (Double.isNaN(wt) || Double.isNaN(wo)) {
363 if (errors != null) { 358 if (errors != null) {
364 errors.addProblem( 359 errors.addProblem(
365 km, "cannot.find.w.for.q", iqs[i]); 360 km, "cannot.find.w.for.q", iqs[i]);
366 } 361 }
367 ows[i] = Double.NaN; 362 ows[i] = Double.NaN;
368 } 363 }
369 else { 364 else {
370 ows[i] = Linear.weight(kmWeight, wt, wo); 365 ows[i] = Linear.weight(kmWeight, wt, wo);
379 } 374 }
380 } 375 }
381 376
382 377
383 public SplineFunction createSpline( 378 public SplineFunction createSpline(
384 WstValueTable table, 379 final WstValueTable table,
385 Calculation errors 380 final Calculation errors
386 ) { 381 ) {
387 double[][] sortedWQ = getSortedWQ(table, errors); 382 final double[][] sortedWQ = getSortedWQ(table, errors);
388 383
389 try { 384 try {
390 SplineInterpolator interpolator = new SplineInterpolator(); 385 final SplineInterpolator interpolator = new SplineInterpolator();
391 PolynomialSplineFunction spline = 386 final PolynomialSplineFunction spline =
392 interpolator.interpolate(sortedWQ[1], sortedWQ[0]); 387 interpolator.interpolate(sortedWQ[1], sortedWQ[0]);
393 388
394 return new SplineFunction(spline, sortedWQ[1], ws); 389 return new SplineFunction(spline, sortedWQ[1], this.ws);
395 } 390 }
396 catch (MathIllegalArgumentException miae) { 391 catch (final MathIllegalArgumentException miae) {
392 if (errors != null) {
393 errors.addProblem(this.km, "spline.creation.failed");
394 }
395 log.error("spline creation failed", miae);
396 }
397 return null;
398 }
399
400 public SplineFunction createSpline(
401 final Row other,
402 final double km,
403 final WstValueTable table,
404 final Calculation errors
405 ) {
406 final double[][] sortedWQ = getSortedWQ(other, km, table, errors);
407
408 final SplineInterpolator interpolator = new SplineInterpolator();
409
410 try {
411 final PolynomialSplineFunction spline =
412 interpolator.interpolate(sortedWQ[1], sortedWQ[0]);
413
414 return new SplineFunction(spline, sortedWQ[1], sortedWQ[0]);
415 }
416 catch (final MathIllegalArgumentException miae) {
397 if (errors != null) { 417 if (errors != null) {
398 errors.addProblem(km, "spline.creation.failed"); 418 errors.addProblem(km, "spline.creation.failed");
399 } 419 }
400 log.error("spline creation failed", miae); 420 log.error("spline creation failed", miae);
401 } 421 }
422
402 return null; 423 return null;
403 } 424 }
404 425
405 public SplineFunction createSpline(
406 Row other,
407 double km,
408 WstValueTable table,
409 Calculation errors
410 ) {
411 double[][] sortedWQ = getSortedWQ(other, km, table, errors);
412
413 SplineInterpolator interpolator = new SplineInterpolator();
414
415 try {
416 PolynomialSplineFunction spline =
417 interpolator.interpolate(sortedWQ[1], sortedWQ[0]);
418
419 return new SplineFunction(spline, sortedWQ[1], sortedWQ[0]);
420 }
421 catch (MathIllegalArgumentException miae) {
422 if (errors != null) {
423 errors.addProblem(km, "spline.creation.failed");
424 }
425 log.error("spline creation failed", miae);
426 }
427
428 return null;
429 }
430
431 public double [][] interpolateWQ( 426 public double [][] interpolateWQ(
432 Row other, 427 final Row other,
433 double km, 428 final double km,
434 int steps, 429 final int steps,
435 WstValueTable table, 430 final WstValueTable table,
436 Calculation errors 431 final Calculation errors
437 ) { 432 ) {
438 SplineFunction sf = createSpline(other, km, table, errors); 433 final SplineFunction sf = createSpline(other, km, table, errors);
439 434
440 return sf != null 435 return sf != null
441 ? sf.sample(steps, km, errors) 436 ? sf.sample(steps, km, errors)
442 : new double[2][0]; 437 : new double[2][0];
443 } 438 }
444 439
445 440
446 public double [][] interpolateWQ( 441 public double [][] interpolateWQ(
447 int steps, 442 final int steps,
448 WstValueTable table, 443 final WstValueTable table,
449 Calculation errors 444 final Calculation errors
450 ) { 445 ) {
451 SplineFunction sf = createSpline(table, errors); 446 final SplineFunction sf = createSpline(table, errors);
452 447
453 return sf != null 448 return sf != null
454 ? sf.sample(steps, km, errors) 449 ? sf.sample(steps, this.km, errors)
455 : new double[2][0]; 450 : new double[2][0];
456 } 451 }
457 452
458 453
459 public double getW(QPosition qPosition) { 454 public double getW(final QPosition qPosition) {
460 int index = qPosition.index; 455 final int index = qPosition.index;
461 double weight = qPosition.weight; 456 final double weight = qPosition.weight;
462 457
463 return weight == 1.0 458 return weight == 1.0
464 ? ws[index] 459 ? this.ws[index]
465 : Linear.weight(weight, ws[index-1], ws[index]); 460 : Linear.weight(weight, this.ws[index-1], this.ws[index]);
466 } 461 }
467 462
468 public double getW( 463 public double getW(
469 Row other, 464 final Row other,
470 double km, 465 final double km,
471 QPosition qPosition 466 final QPosition qPosition
472 ) { 467 ) {
473 double kmWeight = Linear.factor(km, this.km, other.km); 468 final double kmWeight = Linear.factor(km, this.km, other.km);
474 469
475 int index = qPosition.index; 470 final int index = qPosition.index;
476 double weight = qPosition.weight; 471 final double weight = qPosition.weight;
477 472
478 double tw, ow; 473 double tw, ow;
479 474
480 if (weight == 1.0) { 475 if (weight == 1.0) {
481 tw = ws[index]; 476 tw = this.ws[index];
482 ow = other.ws[index]; 477 ow = other.ws[index];
483 } 478 }
484 else { 479 else {
485 tw = Linear.weight(weight, ws[index-1], ws[index]); 480 tw = Linear.weight(weight, this.ws[index-1], this.ws[index]);
486 ow = Linear.weight(weight, other.ws[index-1], other.ws[index]); 481 ow = Linear.weight(weight, other.ws[index-1], other.ws[index]);
487 } 482 }
488 483
489 return Linear.weight(kmWeight, tw, ow); 484 return Linear.weight(kmWeight, tw, ow);
490 } 485 }
491 486
492 public double [] findQsForW( 487 public double [] findQsForW(
493 double w, 488 final double w,
494 WstValueTable table, 489 final WstValueTable table,
495 Calculation errors 490 final Calculation errors
496 ) { 491 ) {
497 log.debug("Find Qs for given W at tabulated km " + km); 492 log.debug("Find Qs for given W at tabulated km " + this.km);
498 return findQsForW(w, getSortedWQ(table, errors)); 493 return findQsForW(w, getSortedWQ(table, errors));
499 } 494 }
500 495
501 public double [] findQsForW( 496 public double [] findQsForW(
502 Row other, 497 final Row other,
503 double w, 498 final double w,
504 double km, 499 final double km,
505 WstValueTable table, 500 final WstValueTable table,
506 Calculation errors 501 final Calculation errors
507 ) { 502 ) {
508 log.debug("Find Qs for given W at non-tabulated km " + km); 503 log.debug("Find Qs for given W at non-tabulated km " + km);
509 return findQsForW(w, getSortedWQ(other, km, table, errors)); 504 return findQsForW(w, getSortedWQ(other, km, table, errors));
510 } 505 }
511 506
512 public double [] getMinMaxW(double [] result) { 507 public double [] getMinMaxW(final double [] result) {
513 double minW = Double.MAX_VALUE; 508 double minW = Double.MAX_VALUE;
514 double maxW = -Double.MAX_VALUE; 509 double maxW = -Double.MAX_VALUE;
515 for (int i = 0; i < ws.length; ++i) { 510 for (int i = 0; i < this.ws.length; ++i) {
516 double w = ws[i]; 511 final double w = this.ws[i];
517 if (w < minW) minW = w; 512 if (w < minW) minW = w;
518 if (w > maxW) maxW = w; 513 if (w > maxW) maxW = w;
519 } 514 }
520 result[0] = minW; 515 result[0] = minW;
521 result[1] = maxW; 516 result[1] = maxW;
522 return result; 517 return result;
523 } 518 }
524 519
525 public double [] getMinMaxW(Row other, double km, double [] result) { 520 public double [] getMinMaxW(final Row other, final double km, final double [] result) {
526 double [] m1 = this .getMinMaxW(new double [2]); 521 final double [] m1 = this .getMinMaxW(new double [2]);
527 double [] m2 = other.getMinMaxW(new double [2]); 522 final double [] m2 = other.getMinMaxW(new double [2]);
528 double factor = Linear.factor(km, this.km, other.km); 523 final double factor = Linear.factor(km, this.km, other.km);
529 result[0] = Linear.weight(factor, m1[0], m2[0]); 524 result[0] = Linear.weight(factor, m1[0], m2[0]);
530 result[1] = Linear.weight(factor, m1[1], m2[1]); 525 result[1] = Linear.weight(factor, m1[1], m2[1]);
531 return result; 526 return result;
532 } 527 }
533 } // class Row 528 } // class Row
537 532
538 /** Columns in table. */ 533 /** Columns in table. */
539 protected Column [] columns; 534 protected Column [] columns;
540 535
541 public WstValueTable() { 536 public WstValueTable() {
542 rows = new ArrayList<Row>(); 537 this.rows = new ArrayList<>();
543 } 538 }
544 539
545 public WstValueTable(Column [] columns) { 540 public WstValueTable(final Column [] columns) {
546 this(); 541 this();
547 this.columns = columns; 542 this.columns = columns;
548 } 543 }
549 544
550 /** 545 /**
551 * @param columns The WST-columns. 546 * @param columns The WST-columns.
552 * @param rows A list of Rows that must be sorted by km. 547 * @param rows A list of Rows that must be sorted by km.
553 */ 548 */
554 public WstValueTable(Column [] columns, List<Row> rows) { 549 public WstValueTable(final Column [] columns, final List<Row> rows) {
555 this.columns = columns; 550 this.columns = columns;
556 this.rows = rows; 551 this.rows = rows;
557 } 552 }
558 553
559 public Column [] getColumns() { 554 public Column [] getColumns() {
560 return columns; 555 return this.columns;
561 } 556 }
562 557
563 /** 558 /**
564 * @param km Given kilometer. 559 * @param km Given kilometer.
565 * @param qs Given Q values. 560 * @param qs Given Q values.
566 * @param ws output parameter. 561 * @param ws output parameter.
567 */ 562 */
568 public double [] interpolateW(double km, double [] qs, double [] ws) { 563 public double [] interpolateW(final double km, final double [] qs, final double [] ws) {
569 return interpolateW(km, qs, ws, null); 564 return interpolateW(km, qs, ws, null);
570 } 565 }
571 566
572 567
573 /** 568 /**
574 * @param ws (output parameter), gets returned. 569 * @param ws (output parameter), gets returned.
575 * @return output parameter ws. 570 * @return output parameter ws.
576 */ 571 */
577 public double [] interpolateW( 572 public double [] interpolateW(
578 double km, 573 final double km,
579 double [] qs, 574 final double [] qs,
580 double [] ws, 575 final double [] ws,
581 Calculation errors 576 final Calculation errors
582 ) { 577 ) {
583 int rowIndex = Collections.binarySearch(rows, new Row(km)); 578 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
584 579
585 QPosition qPosition = new QPosition(); 580 final QPosition qPosition = new QPosition();
586 581
587 if (rowIndex >= 0) { // direct row match 582 if (rowIndex >= 0) { // direct row match
588 Row row = rows.get(rowIndex); 583 final Row row = this.rows.get(rowIndex);
589 for (int i = 0; i < qs.length; ++i) { 584 for (int i = 0; i < qs.length; ++i) {
590 if (getQPosition(km, qs[i], qPosition) == null) { 585 if (getQPosition(km, qs[i], qPosition) == null) {
591 if (errors != null) { 586 if (errors != null) {
592 errors.addProblem(km, "cannot.find.q", qs[i]); 587 errors.addProblem(km, "cannot.find.q", qs[i]);
593 } 588 }
594 ws[i] = Double.NaN; 589 ws[i] = Double.NaN;
595 } 590 }
596 else { 591 else {
597 if (Double.isNaN(ws[i] = row.getW(qPosition)) 592 if (Double.isNaN(ws[i] = row.getW(qPosition))
598 && errors != null) { 593 && errors != null) {
599 errors.addProblem( 594 errors.addProblem(
600 km, "cannot.find.w.for.q", qs[i]); 595 km, "cannot.find.w.for.q", qs[i]);
601 } 596 }
602 } 597 }
603 } 598 }
604 } 599 }
605 else { // needs bilinear interpolation 600 else { // needs bilinear interpolation
606 rowIndex = -rowIndex -1; 601 rowIndex = -rowIndex -1;
607 602
608 if (rowIndex < 1 || rowIndex >= rows.size()) { 603 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
609 // do not extrapolate 604 // do not extrapolate
610 Arrays.fill(ws, Double.NaN); 605 Arrays.fill(ws, Double.NaN);
611 if (errors != null) { 606 if (errors != null) {
612 errors.addProblem(km, "km.not.found"); 607 errors.addProblem(km, "km.not.found");
613 } 608 }
614 } 609 }
615 else { 610 else {
616 Row r1 = rows.get(rowIndex-1); 611 final Row r1 = this.rows.get(rowIndex-1);
617 Row r2 = rows.get(rowIndex); 612 final Row r2 = this.rows.get(rowIndex);
618 r1.interpolateW(r2, km, qs, ws, this, errors); 613 r1.interpolateW(r2, km, qs, ws, this, errors);
619 } 614 }
620 } 615 }
621 616
622 return ws; 617 return ws;
623 } 618 }
624 619
625 public double [] getMinMaxQ(double km) { 620 /**
621 * Interpolates a W for a km using a Q column position
622 */
623 public double interpolateW(final double km, final QPosition qPosition, final Calculation errors) {
624
625 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
626
627 if (rowIndex >= 0) { // direct row match
628 final Row row = this.rows.get(rowIndex);
629 return row.getW(qPosition);
630 }
631 else { // needs bilinear interpolation
632 rowIndex = -rowIndex - 1;
633 if ((rowIndex <= 0) || (rowIndex >= this.rows.size()))
634 return Double.NaN;
635 else {
636 final Row r1 = this.rows.get(rowIndex - 1);
637 final Row r2 = this.rows.get(rowIndex);
638 final double w1 = r1.getW(qPosition);
639 final double w2 = r2.getW(qPosition);
640 if (Double.isNaN(w1) || Double.isNaN(w2))
641 return Double.NaN;
642 else {
643 final double kmWeight = Linear.factor(km, r1.km, r2.km);
644 return Linear.weight(kmWeight, w1, w2);
645 }
646 }
647 }
648 }
649
650 public double [] getMinMaxQ(final double km) {
626 return getMinMaxQ(km, new double [2]); 651 return getMinMaxQ(km, new double [2]);
627 } 652 }
628 653
629 public double [] getMinMaxQ(double km, double [] result) { 654 public double [] getMinMaxQ(final double km, final double [] result) {
630 double minQ = Double.MAX_VALUE; 655 double minQ = Double.MAX_VALUE;
631 double maxQ = -Double.MAX_VALUE; 656 double maxQ = -Double.MAX_VALUE;
632 657
633 for (int i = 0; i < columns.length; ++i) { 658 for (int i = 0; i < this.columns.length; ++i) {
634 double q = columns[i].getQRangeTree().findQ(km); 659 final double q = this.columns[i].getQRangeTree().findQ(km);
635 if (!Double.isNaN(q)) { 660 if (!Double.isNaN(q)) {
636 if (q < minQ) minQ = q; 661 if (q < minQ) minQ = q;
637 if (q > maxQ) maxQ = q; 662 if (q > maxQ) maxQ = q;
638 } 663 }
639 } 664 }
646 671
647 return null; 672 return null;
648 } 673 }
649 674
650 public double [] getMinMaxQ(double from, double to, double step) { 675 public double [] getMinMaxQ(double from, double to, double step) {
651 double [] result = new double[2]; 676 final double [] result = new double[2];
652 677
653 double minQ = Double.MAX_VALUE; 678 double minQ = Double.MAX_VALUE;
654 double maxQ = -Double.MAX_VALUE; 679 double maxQ = -Double.MAX_VALUE;
655 680
656 if (from > to) { 681 if (from > to) {
657 double tmp = from; 682 final double tmp = from;
658 from = to; 683 from = to;
659 to = tmp; 684 to = tmp;
660 } 685 }
661 686
662 step = Math.max(Math.abs(step), 0.0001); 687 step = Math.max(Math.abs(step), 0.0001);
675 if (result[1] > maxQ) maxQ = result[1]; 700 if (result[1] > maxQ) maxQ = result[1];
676 } 701 }
677 } 702 }
678 703
679 return minQ < Double.MAX_VALUE 704 return minQ < Double.MAX_VALUE
680 ? new double [] { minQ, maxQ } 705 ? new double [] { minQ, maxQ }
681 : null; 706 : null;
682 } 707 }
683 708
684 public double [] getMinMaxW(double km) { 709 public double [] getMinMaxW(final double km) {
685 return getMinMaxW(km, new double [2]); 710 return getMinMaxW(km, new double [2]);
686 711
687 } 712 }
688 public double [] getMinMaxW(double km, double [] result) { 713 public double [] getMinMaxW(final double km, final double [] result) {
689 int rowIndex = Collections.binarySearch(rows, new Row(km)); 714 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
690 715
691 if (rowIndex >= 0) { 716 if (rowIndex >= 0) {
692 return rows.get(rowIndex).getMinMaxW(result); 717 return this.rows.get(rowIndex).getMinMaxW(result);
693 } 718 }
694 719
695 rowIndex = -rowIndex -1; 720 rowIndex = -rowIndex -1;
696 721
697 if (rowIndex < 1 || rowIndex >= rows.size()) { 722 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
698 // do not extrapolate 723 // do not extrapolate
699 return null; 724 return null;
700 } 725 }
701 726
702 Row r1 = rows.get(rowIndex-1); 727 final Row r1 = this.rows.get(rowIndex-1);
703 Row r2 = rows.get(rowIndex); 728 final Row r2 = this.rows.get(rowIndex);
704 729
705 return r1.getMinMaxW(r2, km, result); 730 return r1.getMinMaxW(r2, km, result);
706 } 731 }
707 732
708 public double [] getMinMaxW(double from, double to, double step) { 733 public double [] getMinMaxW(double from, double to, double step) {
709 double [] result = new double[2]; 734 final double [] result = new double[2];
710 735
711 double minW = Double.MAX_VALUE; 736 double minW = Double.MAX_VALUE;
712 double maxW = -Double.MAX_VALUE; 737 double maxW = -Double.MAX_VALUE;
713 738
714 if (from > to) { 739 if (from > to) {
715 double tmp = from; 740 final double tmp = from;
716 from = to; 741 from = to;
717 to = tmp; 742 to = tmp;
718 } 743 }
719 744
720 step = Math.max(Math.abs(step), 0.0001); 745 step = Math.max(Math.abs(step), 0.0001);
733 if (result[1] > maxW) maxW = result[1]; 758 if (result[1] > maxW) maxW = result[1];
734 } 759 }
735 } 760 }
736 761
737 return minW < Double.MAX_VALUE 762 return minW < Double.MAX_VALUE
738 ? new double [] { minW, maxW } 763 ? new double [] { minW, maxW }
739 : null; 764 : null;
740 } 765 }
741 766
742 /** 767 /**
743 * Interpolate W and Q values at a given km. 768 * Interpolate W and Q values at a given km.
744 */ 769 */
745 public double [][] interpolateWQ(double km) { 770 public double [][] interpolateWQ(final double km) {
746 return interpolateWQ(km, null); 771 return interpolateWQ(km, null);
747 } 772 }
748 773
749 /** 774 /**
750 * Interpolate W and Q values at a given km. 775 * Interpolate W and Q values at a given km.
751 * 776 *
752 * @param errors where to store errors. 777 * @param errors where to store errors.
753 * 778 *
754 * @return double double array, first index Ws, second Qs. 779 * @return double double array, first index Ws, second Qs.
755 */ 780 */
756 public double [][] interpolateWQ(double km, Calculation errors) { 781 public double [][] interpolateWQ(final double km, final Calculation errors) {
757 return interpolateWQ(km, DEFAULT_Q_STEPS, errors); 782 return interpolateWQ(km, DEFAULT_Q_STEPS, errors);
758 } 783 }
759 784
760 785
761 /** 786 /**
762 * Interpolate W and Q values at a given km. 787 * Interpolate W and Q values at a given km.
763 */ 788 */
764 public double [][] interpolateWQ( 789 public double [][] interpolateWQ(
765 double km, 790 final double km,
766 int steps, 791 final int steps,
767 Calculation errors 792 final Calculation errors
768 ) { 793 ) {
769 int rowIndex = Collections.binarySearch(rows, new Row(km)); 794 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
770 795
771 if (rowIndex >= 0) { // direct row match 796 if (rowIndex >= 0) { // direct row match
772 Row row = rows.get(rowIndex); 797 final Row row = this.rows.get(rowIndex);
773 return row.interpolateWQ(steps, this, errors); 798 return row.interpolateWQ(steps, this, errors);
774 } 799 }
775 800
776 rowIndex = -rowIndex -1; 801 rowIndex = -rowIndex -1;
777 802
778 if (rowIndex < 1 || rowIndex >= rows.size()) { 803 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
779 // do not extrapolate 804 // do not extrapolate
780 if (errors != null) { 805 if (errors != null) {
781 errors.addProblem(km, "km.not.found"); 806 errors.addProblem(km, "km.not.found");
782 } 807 }
783 return new double[2][0]; 808 return new double[2][0];
784 } 809 }
785 810
786 Row r1 = rows.get(rowIndex-1); 811 final Row r1 = this.rows.get(rowIndex-1);
787 Row r2 = rows.get(rowIndex); 812 final Row r2 = this.rows.get(rowIndex);
788 813
789 return r1.interpolateWQ(r2, km, steps, this, errors); 814 return r1.interpolateWQ(r2, km, steps, this, errors);
790 } 815 }
791 816
792 public boolean interpolate( 817 public boolean interpolate(
793 double km, 818 final double km,
794 double [] out, 819 final double [] out,
795 QPosition qPosition, 820 final QPosition qPosition,
796 Function qFunction 821 final Function qFunction
797 ) { 822 ) {
798 int R1 = rows.size()-1; 823 final int R1 = this.rows.size()-1;
799 824
800 out[1] = qFunction.value(getQ(qPosition, km)); 825 out[1] = qFunction.value(getQ(qPosition, km));
801 826
802 if (Double.isNaN(out[1])) { 827 if (Double.isNaN(out[1])) {
803 return false; 828 return false;
804 } 829 }
805 830
806 QPosition nPosition = new QPosition(); 831 final QPosition nPosition = new QPosition();
807 if (getQPosition(km, out[1], nPosition) == null) { 832 if (getQPosition(km, out[1], nPosition) == null) {
808 return false; 833 return false;
809 } 834 }
810 835
811 int rowIndex = Collections.binarySearch(rows, new Row(km)); 836 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
812 837
813 if (rowIndex >= 0) { 838 if (rowIndex >= 0) {
814 // direct row match 839 // direct row match
815 out[0] = rows.get(rowIndex).getW(nPosition); 840 out[0] = this.rows.get(rowIndex).getW(nPosition);
816 return !Double.isNaN(out[0]); 841 return !Double.isNaN(out[0]);
817 } 842 }
818 843
819 rowIndex = -rowIndex -1; 844 rowIndex = -rowIndex -1;
820 845
821 if (rowIndex < 1 || rowIndex > R1) { 846 if (rowIndex < 1 || rowIndex > R1) {
822 // do not extrapolate 847 // do not extrapolate
823 return false; 848 return false;
824 } 849 }
825 850
826 Row r1 = rows.get(rowIndex-1); 851 final Row r1 = this.rows.get(rowIndex-1);
827 Row r2 = rows.get(rowIndex); 852 final Row r2 = this.rows.get(rowIndex);
828 out[0] = r1.getW(r2, km, nPosition); 853 out[0] = r1.getW(r2, km, nPosition);
829 854
830 return !Double.isNaN(out[0]); 855 return !Double.isNaN(out[0]);
831 } 856 }
832 857
840 * @param ws (output) resulting interpolated ws. 865 * @param ws (output) resulting interpolated ws.
841 * @param qs (output) resulting interpolated qs. 866 * @param qs (output) resulting interpolated qs.
842 * @param errors calculation object to store errors. 867 * @param errors calculation object to store errors.
843 */ 868 */
844 public QPosition interpolate( 869 public QPosition interpolate(
845 double q, 870 final double q,
846 double referenceKm, 871 final double referenceKm,
847 double [] kms, 872 final double [] kms,
848 double [] ws, 873 final double [] ws,
849 double [] qs, 874 final double [] qs,
850 Calculation errors 875 final Calculation errors
851 ) { 876 ) {
852 return interpolate( 877 return interpolate(
853 q, referenceKm, kms, ws, qs, 0, kms.length, errors); 878 q, referenceKm, kms, ws, qs, 0, kms.length, errors);
854 } 879 }
855 880
856 /** 881 /**
857 * Interpolate Q at given positions. 882 * Interpolate Q at given positions.
858 * @param kms positions for which to calculate qs and ws 883 * @param kms positions for which to calculate qs and ws
859 * @param ws [out] calculated ws for kms 884 * @param ws [out] calculated ws for kms
860 * @param qs [out] looked up qs for kms. 885 * @param qs [out] looked up qs for kms.
861 */ 886 */
862 public QPosition interpolate( 887 public QPosition interpolate(
863 double q, 888 final double q,
864 double referenceKm, 889 final double referenceKm,
865 double [] kms, 890 final double [] kms,
866 double [] ws, 891 final double [] ws,
867 double [] qs, 892 final double [] qs,
868 int startIndex, 893 final int startIndex,
869 int length, 894 final int length,
870 Calculation errors 895 final Calculation errors
871 ) { 896 ) {
872 QPosition qPosition = getQPosition(referenceKm, q); 897 final QPosition qPosition = getQPosition(referenceKm, q);
873 898
874 if (qPosition == null) { 899 if (qPosition == null) {
875 // we cannot locate q at km 900 // we cannot locate q at km
876 Arrays.fill(ws, Double.NaN); 901 Arrays.fill(ws, Double.NaN);
877 Arrays.fill(qs, Double.NaN); 902 Arrays.fill(qs, Double.NaN);
879 errors.addProblem(referenceKm, "cannot.find.q", q); 904 errors.addProblem(referenceKm, "cannot.find.q", q);
880 } 905 }
881 return null; 906 return null;
882 } 907 }
883 908
884 Row kmKey = new Row(); 909 final Row kmKey = new Row();
885 910
886 int R1 = rows.size()-1; 911 final int R1 = this.rows.size()-1;
887 912
888 for (int i = startIndex, end = startIndex+length; i < end; ++i) { 913 for (int i = startIndex, end = startIndex+length; i < end; ++i) {
889 914
890 if (Double.isNaN(qs[i] = getQ(qPosition, kms[i]))) { 915 if (Double.isNaN(qs[i] = getQ(qPosition, kms[i]))) {
891 if (errors != null) { 916 if (errors != null) {
894 ws[i] = Double.NaN; 919 ws[i] = Double.NaN;
895 continue; 920 continue;
896 } 921 }
897 922
898 kmKey.km = kms[i]; 923 kmKey.km = kms[i];
899 int rowIndex = Collections.binarySearch(rows, kmKey); 924 int rowIndex = Collections.binarySearch(this.rows, kmKey);
900 925
901 if (rowIndex >= 0) { 926 if (rowIndex >= 0) {
902 // direct row match 927 // direct row match
903 if (Double.isNaN(ws[i] = rows.get(rowIndex).getW(qPosition)) 928 if (Double.isNaN(ws[i] = this.rows.get(rowIndex).getW(qPosition))
904 && errors != null) { 929 && errors != null) {
905 errors.addProblem(kms[i], "cannot.find.w.for.q", q); 930 errors.addProblem(kms[i], "cannot.find.w.for.q", q);
906 } 931 }
907 continue; 932 continue;
908 } 933 }
909 934
915 errors.addProblem(kms[i], "km.not.found"); 940 errors.addProblem(kms[i], "km.not.found");
916 } 941 }
917 ws[i] = Double.NaN; 942 ws[i] = Double.NaN;
918 continue; 943 continue;
919 } 944 }
920 Row r1 = rows.get(rowIndex-1); 945 final Row r1 = this.rows.get(rowIndex-1);
921 Row r2 = rows.get(rowIndex); 946 final Row r2 = this.rows.get(rowIndex);
922 947
923 if (Double.isNaN(ws[i] = r1.getW(r2, kms[i], qPosition)) 948 if (Double.isNaN(ws[i] = r1.getW(r2, kms[i], qPosition))
924 && errors != null) { 949 && errors != null) {
925 errors.addProblem(kms[i], "cannot.find.w.for.q", q); 950 errors.addProblem(kms[i], "cannot.find.w.for.q", q);
926 } 951 }
927 } 952 }
928 953
929 return qPosition; 954 return qPosition;
937 * @param row2 second row. 962 * @param row2 second row.
938 * @param col column-index at which to look. 963 * @param col column-index at which to look.
939 * 964 *
940 * @return Linearly interpolated w, NaN if one of the given rows was null. 965 * @return Linearly interpolated w, NaN if one of the given rows was null.
941 */ 966 */
942 public static double linearW(double km, Row row1, Row row2, int col) { 967 public static double linearW(final double km, final Row row1, final Row row2, final int col) {
943 if (row1 == null || row2 == null) { 968 if (row1 == null || row2 == null) {
944 return Double.NaN; 969 return Double.NaN;
945 } 970 }
946 971
947 return Linear.linear(km, 972 return Linear.linear(km,
948 row1.km, row2.km, 973 row1.km, row2.km,
949 row1.ws[col], row2.ws[col]); 974 row1.ws[col], row2.ws[col]);
950 } 975 }
951 976
952 /** 977 /**
953 * Do interpolation/lookup of W and Q within columns (i.e. ignoring values 978 * Do interpolation/lookup of W and Q within columns (i.e. ignoring values
954 * of other columns). 979 * of other columns).
955 * @param km position (km) at which to interpolate/lookup. 980 * @param km position (km) at which to interpolate/lookup.
956 * @return [[q0, q1, .. qx] , [w0, w1, .. wx]] (can contain NaNs) 981 * @return [[q0, q1, .. qx] , [w0, w1, .. wx]] (can contain NaNs)
957 */ 982 */
958 public double [][] interpolateWQColumnwise(double km) { 983 public double [][] interpolateWQColumnwise(final double km) {
959 log.debug("WstValueTable.interpolateWQColumnwise"); 984 log.debug("WstValueTable.interpolateWQColumnwise");
960 double [] qs = new double[columns.length]; 985 final double [] qs = new double[this.columns.length];
961 double [] ws = new double[columns.length]; 986 final double [] ws = new double[this.columns.length];
962 987
963 // Find out row from where we will start searching. 988 // Find out row from where we will start searching.
964 int rowIndex = Collections.binarySearch(rows, new Row(km)); 989 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
965 990
966 if (rowIndex < 0) { 991 if (rowIndex < 0) {
967 rowIndex = -rowIndex -1; 992 rowIndex = -rowIndex -1;
968 } 993 }
969 994
970 // TODO Beyond definition, we could stop more clever. 995 // TODO Beyond definition, we could stop more clever.
971 if (rowIndex >= rows.size()) { 996 if (rowIndex >= this.rows.size()) {
972 rowIndex = rows.size() -1; 997 rowIndex = this.rows.size() -1;
973 } 998 }
974 999
975 Row startRow = rows.get(rowIndex); 1000 final Row startRow = this.rows.get(rowIndex);
976 1001
977 for (int col = 0; col < columns.length; col++) { 1002 for (int col = 0; col < this.columns.length; col++) {
978 qs[col] = columns[col].getQRangeTree().findQ(km); 1003 qs[col] = this.columns[col].getQRangeTree().findQ(km);
979 if (startRow.km == km && !Double.isNaN(startRow.ws[col])) { 1004 if (startRow.km == km && !Double.isNaN(startRow.ws[col])) {
980 // Great. W is defined at km. 1005 // Great. W is defined at km.
981 ws[col] = startRow.ws[col]; 1006 ws[col] = startRow.ws[col];
982 continue; 1007 continue;
983 } 1008 }
984 1009
985 // Search neighbouring rows that define w at this col. 1010 // Search neighbouring rows that define w at this col.
986 Row rowBefore = null; 1011 Row rowBefore = null;
987 Row rowAfter = null; 1012 Row rowAfter = null;
988 for (int before = rowIndex -1; before >= 0; before--) { 1013 for (int before = rowIndex -1; before >= 0; before--) {
989 if (!Double.isNaN(rows.get(before).ws[col])) { 1014 if (!Double.isNaN(this.rows.get(before).ws[col])) {
990 rowBefore = rows.get(before); 1015 rowBefore = this.rows.get(before);
991 break; 1016 break;
992 } 1017 }
993 } 1018 }
994 if (rowBefore != null) { 1019 if (rowBefore != null) {
995 for (int after = rowIndex, R = rows.size(); 1020 for (int after = rowIndex, R = this.rows.size();
996 after < R; 1021 after < R;
997 after++ 1022 after++
998 ) { 1023 ) {
999 if (!Double.isNaN(rows.get(after).ws[col])) { 1024 if (!Double.isNaN(this.rows.get(after).ws[col])) {
1000 rowAfter = rows.get(after); 1025 rowAfter = this.rows.get(after);
1001 break; 1026 break;
1002 } 1027 }
1003 } 1028 }
1004 } 1029 }
1005 1030
1007 } 1032 }
1008 1033
1009 return new double [][] {qs, ws}; 1034 return new double [][] {qs, ws};
1010 } 1035 }
1011 1036
1012 public double [] findQsForW(double km, double w, Calculation errors) { 1037 public double [] findQsForW(final double km, final double w, final Calculation errors) {
1013 1038
1014 int rowIndex = Collections.binarySearch(rows, new Row(km)); 1039 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
1015 1040
1016 if (rowIndex >= 0) { 1041 if (rowIndex >= 0) {
1017 return rows.get(rowIndex).findQsForW(w, this, errors); 1042 return this.rows.get(rowIndex).findQsForW(w, this, errors);
1018 } 1043 }
1019 1044
1020 rowIndex = -rowIndex - 1; 1045 rowIndex = -rowIndex - 1;
1021 1046
1022 if (rowIndex < 1 || rowIndex >= rows.size()) { 1047 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
1023 // Do not extrapolate. 1048 // Do not extrapolate.
1024 return new double[0]; 1049 return new double[0];
1025 } 1050 }
1026 1051
1027 // Needs bilinear interpolation. 1052 // Needs bilinear interpolation.
1028 Row r1 = rows.get(rowIndex-1); 1053 final Row r1 = this.rows.get(rowIndex-1);
1029 Row r2 = rows.get(rowIndex); 1054 final Row r2 = this.rows.get(rowIndex);
1030 1055
1031 return r1.findQsForW(r2, w, km, this, errors); 1056 return r1.findQsForW(r2, w, km, this, errors);
1032 } 1057 }
1033 1058
1034 protected SplineFunction createSpline(double km, Calculation errors) { 1059 protected SplineFunction createSpline(final double km, final Calculation errors) {
1035 1060
1036 int rowIndex = Collections.binarySearch(rows, new Row(km)); 1061 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
1037 1062
1038 if (rowIndex >= 0) { 1063 if (rowIndex >= 0) {
1039 SplineFunction sf = rows.get(rowIndex).createSpline(this, errors); 1064 final SplineFunction sf = this.rows.get(rowIndex).createSpline(this, errors);
1040 if (sf == null && errors != null) { 1065 if (sf == null && errors != null) {
1041 errors.addProblem(km, "cannot.create.wq.relation"); 1066 errors.addProblem(km, "cannot.create.wq.relation");
1042 } 1067 }
1043 return sf; 1068 return sf;
1044 } 1069 }
1045 1070
1046 rowIndex = -rowIndex - 1; 1071 rowIndex = -rowIndex - 1;
1047 1072
1048 if (rowIndex < 1 || rowIndex >= rows.size()) { 1073 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
1049 // Do not extrapolate. 1074 // Do not extrapolate.
1050 if (errors != null) { 1075 if (errors != null) {
1051 errors.addProblem(km, "km.not.found"); 1076 errors.addProblem(km, "km.not.found");
1052 } 1077 }
1053 return null; 1078 return null;
1054 } 1079 }
1055 1080
1056 // Needs bilinear interpolation. 1081 // Needs bilinear interpolation.
1057 Row r1 = rows.get(rowIndex-1); 1082 final Row r1 = this.rows.get(rowIndex-1);
1058 Row r2 = rows.get(rowIndex); 1083 final Row r2 = this.rows.get(rowIndex);
1059 1084
1060 SplineFunction sf = r1.createSpline(r2, km, this, errors); 1085 final SplineFunction sf = r1.createSpline(r2, km, this, errors);
1061 if (sf == null && errors != null) { 1086 if (sf == null && errors != null) {
1062 errors.addProblem(km, "cannot.create.wq.relation"); 1087 errors.addProblem(km, "cannot.create.wq.relation");
1063 } 1088 }
1064 1089
1065 return sf; 1090 return sf;
1066 } 1091 }
1067 1092
1068 /** 'Bezugslinienverfahren' */ 1093 /** 'Bezugslinienverfahren' */
1069 public double [][] relateWs( 1094 public double [][] relateWs(
1070 double km1, 1095 final double km1,
1071 double km2, 1096 final double km2,
1072 Calculation errors 1097 final Calculation errors
1073 ) { 1098 ) {
1074 return relateWs(km1, km2, RELATE_WS_SAMPLES, errors); 1099 return relateWs(km1, km2, RELATE_WS_SAMPLES, errors);
1075 } 1100 }
1076 1101
1077 private static class ErrorHandler { 1102 private static class ErrorHandler {
1078 1103
1079 boolean hasErrors; 1104 boolean hasErrors;
1080 Calculation errors; 1105 Calculation errors;
1081 1106
1082 ErrorHandler(Calculation errors) { 1107 ErrorHandler(final Calculation errors) {
1083 this.errors = errors; 1108 this.errors = errors;
1084 } 1109 }
1085 1110
1086 void error(double km, String key, Object ... args) { 1111 void error(final double km, final String key, final Object ... args) {
1087 if (errors != null && !hasErrors) { 1112 if (this.errors != null && !this.hasErrors) {
1088 hasErrors = true; 1113 this.hasErrors = true;
1089 errors.addProblem(km, key, args); 1114 this.errors.addProblem(km, key, args);
1090 } 1115 }
1091 } 1116 }
1092 } // class ErrorHandler 1117 } // class ErrorHandler
1093 1118
1094 1119
1095 /* TODO: Add optimized methods of relateWs to relate one 1120 /* TODO: Add optimized methods of relateWs to relate one
1096 * start km to many end kms. The index generation/spline stuff for 1121 * start km to many end kms. The index generation/spline stuff for
1097 * the start km is always the same. 1122 * the start km is always the same.
1098 */ 1123 */
1099 public double [][] relateWs( 1124 public double [][] relateWs(
1100 double km1, 1125 final double km1,
1101 double km2, 1126 final double km2,
1102 int numSamples, 1127 final int numSamples,
1103 Calculation errors 1128 final Calculation errors
1104 ) { 1129 ) {
1105 SplineFunction sf1 = createSpline(km1, errors); 1130 final SplineFunction sf1 = createSpline(km1, errors);
1106 if (sf1 == null) { 1131 if (sf1 == null) {
1107 return new double[2][0]; 1132 return new double[2][0];
1108 } 1133 }
1109 1134
1110 SplineFunction sf2 = createSpline(km2, errors); 1135 final SplineFunction sf2 = createSpline(km2, errors);
1111 if (sf2 == null) { 1136 if (sf2 == null) {
1112 return new double[2][0]; 1137 return new double[2][0];
1113 } 1138 }
1114 1139
1115 PolynomialSplineFunction iQ1 = sf1.createIndexQRelation(); 1140 final PolynomialSplineFunction iQ1 = sf1.createIndexQRelation();
1116 if (iQ1 == null) { 1141 if (iQ1 == null) {
1117 if (errors != null) { 1142 if (errors != null) {
1118 errors.addProblem(km1, "cannot.create.index.q.relation"); 1143 errors.addProblem(km1, "cannot.create.index.q.relation");
1119 } 1144 }
1120 return new double[2][0]; 1145 return new double[2][0];
1121 } 1146 }
1122 1147
1123 PolynomialSplineFunction iQ2 = sf2.createIndexQRelation(); 1148 final PolynomialSplineFunction iQ2 = sf2.createIndexQRelation();
1124 if (iQ2 == null) { 1149 if (iQ2 == null) {
1125 if (errors != null) { 1150 if (errors != null) {
1126 errors.addProblem(km2, "cannot.create.index.q.relation"); 1151 errors.addProblem(km2, "cannot.create.index.q.relation");
1127 } 1152 }
1128 return new double[2][0]; 1153 return new double[2][0];
1129 } 1154 }
1130 1155
1131 int N = Math.min(sf1.splineQs.length, sf2.splineQs.length); 1156 final int N = Math.min(sf1.splineQs.length, sf2.splineQs.length);
1132 double stepWidth = N/(double)numSamples; 1157 final double stepWidth = N/(double)numSamples;
1133 1158
1134 PolynomialSplineFunction qW1 = sf1.spline; 1159 final PolynomialSplineFunction qW1 = sf1.spline;
1135 PolynomialSplineFunction qW2 = sf2.spline; 1160 final PolynomialSplineFunction qW2 = sf2.spline;
1136 1161
1137 TDoubleArrayList ws1 = new TDoubleArrayList(numSamples); 1162 final TDoubleArrayList ws1 = new TDoubleArrayList(numSamples);
1138 TDoubleArrayList ws2 = new TDoubleArrayList(numSamples); 1163 final TDoubleArrayList ws2 = new TDoubleArrayList(numSamples);
1139 TDoubleArrayList qs1 = new TDoubleArrayList(numSamples); 1164 final TDoubleArrayList qs1 = new TDoubleArrayList(numSamples);
1140 TDoubleArrayList qs2 = new TDoubleArrayList(numSamples); 1165 final TDoubleArrayList qs2 = new TDoubleArrayList(numSamples);
1141 1166
1142 ErrorHandler err = new ErrorHandler(errors); 1167 final ErrorHandler err = new ErrorHandler(errors);
1143 1168
1144 int i = 0; 1169 int i = 0;
1145 for (double p = 0d; p <= N-1; p += stepWidth, ++i) { 1170 for (double p = 0d; p <= N-1; p += stepWidth, ++i) {
1146 1171
1147 double q1; 1172 double q1;
1148 try { 1173 try {
1149 q1 = iQ1.value(p); 1174 q1 = iQ1.value(p);
1150 } 1175 }
1151 catch (ArgumentOutsideDomainException aode) { 1176 catch (final ArgumentOutsideDomainException aode) {
1152 err.error(km1, "w.w.qkm1.failed", p); 1177 err.error(km1, "w.w.qkm1.failed", p);
1153 continue; 1178 continue;
1154 } 1179 }
1155 1180
1156 double w1; 1181 double w1;
1157 try { 1182 try {
1158 w1 = qW1.value(q1); 1183 w1 = qW1.value(q1);
1159 } 1184 }
1160 catch (ArgumentOutsideDomainException aode) { 1185 catch (final ArgumentOutsideDomainException aode) {
1161 err.error(km1, "w.w.wkm1.failed", q1, p); 1186 err.error(km1, "w.w.wkm1.failed", q1, p);
1162 continue; 1187 continue;
1163 } 1188 }
1164 1189
1165 double q2; 1190 double q2;
1166 try { 1191 try {
1167 q2 = iQ2.value(p); 1192 q2 = iQ2.value(p);
1168 } 1193 }
1169 catch (ArgumentOutsideDomainException aode) { 1194 catch (final ArgumentOutsideDomainException aode) {
1170 err.error(km2, "w.w.qkm2.failed", p); 1195 err.error(km2, "w.w.qkm2.failed", p);
1171 continue; 1196 continue;
1172 } 1197 }
1173 1198
1174 double w2; 1199 double w2;
1175 try { 1200 try {
1176 w2 = qW2.value(q2); 1201 w2 = qW2.value(q2);
1177 } 1202 }
1178 catch (ArgumentOutsideDomainException aode) { 1203 catch (final ArgumentOutsideDomainException aode) {
1179 err.error(km2, "w.w.wkm2.failed", q2, p); 1204 err.error(km2, "w.w.wkm2.failed", q2, p);
1180 continue; 1205 continue;
1181 } 1206 }
1182 1207
1183 ws1.add(w1); 1208 ws1.add(w1);
1191 qs1.toNativeArray(), 1216 qs1.toNativeArray(),
1192 ws2.toNativeArray(), 1217 ws2.toNativeArray(),
1193 qs2.toNativeArray() }; 1218 qs2.toNativeArray() };
1194 } 1219 }
1195 1220
1196 public QPosition getQPosition(double km, double q) { 1221 public QPosition getQPosition(final double km, final double q) {
1197 return getQPosition(km, q, new QPosition()); 1222 return getQPosition(km, q, new QPosition());
1198 } 1223 }
1199 1224
1200 public QPosition getQPosition(double km, double q, QPosition qPosition) { 1225 public QPosition getQPosition(final double km, final double q, final QPosition qPosition) {
1201 1226
1202 if (columns.length == 0) { 1227 if (this.columns.length == 0) {
1203 return null; 1228 return null;
1204 } 1229 }
1205 1230
1206 double qLast = columns[0].getQRangeTree().findQ(km); 1231 double qLast = this.columns[0].getQRangeTree().findQ(km);
1207 1232
1208 if (Math.abs(qLast - q) < 0.00001) { 1233 if (Math.abs(qLast - q) < 0.00001) {
1209 return qPosition.set(0, 1d); 1234 return qPosition.set(0, 1d);
1210 } 1235 }
1211 1236
1212 for (int i = 1; i < columns.length; ++i) { 1237 for (int i = 1; i < this.columns.length; ++i) {
1213 double qCurrent = columns[i].getQRangeTree().findQ(km); 1238 final double qCurrent = this.columns[i].getQRangeTree().findQ(km);
1214 if (Math.abs(qCurrent - q) < 0.00001) { 1239 if (Math.abs(qCurrent - q) < 0.00001) {
1215 return qPosition.set(i, 1d); 1240 return qPosition.set(i, 1d);
1216 } 1241 }
1217 1242
1218 double qMin, qMax; 1243 double qMin, qMax;
1219 if (qLast < qCurrent) { qMin = qLast; qMax = qCurrent; } 1244 if (qLast < qCurrent) { qMin = qLast; qMax = qCurrent; }
1220 else { qMin = qCurrent; qMax = qLast; } 1245 else { qMin = qCurrent; qMax = qLast; }
1221 1246
1222 if (q > qMin && q < qMax) { 1247 if (q > qMin && q < qMax) {
1223 double weight = Linear.factor(q, qLast, qCurrent); 1248 final double weight = Linear.factor(q, qLast, qCurrent);
1224 return qPosition.set(i, weight); 1249 return qPosition.set(i, weight);
1225 } 1250 }
1226 qLast = qCurrent; 1251 qLast = qCurrent;
1227 } 1252 }
1228 1253
1229 return null; 1254 return null;
1230 } 1255 }
1231 1256
1232 public double getQIndex(int index, double km) { 1257 public double getQIndex(final int index, final double km) {
1233 return columns[index].getQRangeTree().findQ(km); 1258 return this.columns[index].getQRangeTree().findQ(km);
1234 } 1259 }
1235 1260
1236 public double getQ(QPosition qPosition, double km) { 1261 public double getQ(final QPosition qPosition, final double km) {
1237 int index = qPosition.index; 1262 final int index = qPosition.index;
1238 double weight = qPosition.weight; 1263 final double weight = qPosition.weight;
1239 1264
1240 if (weight == 1d) { 1265 if (weight == 1d) {
1241 return columns[index].getQRangeTree().findQ(km); 1266 return this.columns[index].getQRangeTree().findQ(km);
1242 } 1267 }
1243 double q1 = columns[index-1].getQRangeTree().findQ(km); 1268 final double q1 = this.columns[index-1].getQRangeTree().findQ(km);
1244 double q2 = columns[index ].getQRangeTree().findQ(km); 1269 final double q2 = this.columns[index ].getQRangeTree().findQ(km);
1245 return Linear.weight(weight, q1, q2); 1270 return Linear.weight(weight, q1, q2);
1246 } 1271 }
1247 1272
1248 public double [][] interpolateTabulated(double km) { 1273 public double [][] interpolateTabulated(final double km) {
1249 return interpolateTabulated(km, new double[2][columns.length]); 1274 return interpolateTabulated(km, new double[2][this.columns.length]);
1250 } 1275 }
1251 1276
1252 public double [][] interpolateTabulated(double km, double [][] result) { 1277 public double [][] interpolateTabulated(final double km, final double [][] result) {
1253 1278
1254 int rowIndex = Collections.binarySearch(rows, new Row(km)); 1279 int rowIndex = Collections.binarySearch(this.rows, new Row(km));
1255 1280
1256 if (rowIndex >= 0) { 1281 if (rowIndex >= 0) {
1257 // Direct hit -> copy ws. 1282 // Direct hit -> copy ws.
1258 Row row = rows.get(rowIndex); 1283 final Row row = this.rows.get(rowIndex);
1259 System.arraycopy( 1284 System.arraycopy(
1260 row.ws, 0, result[0], 0, 1285 row.ws, 0, result[0], 0,
1261 Math.min(row.ws.length, result[0].length)); 1286 Math.min(row.ws.length, result[0].length));
1262 } 1287 }
1263 else { 1288 else {
1264 rowIndex = -rowIndex -1; 1289 rowIndex = -rowIndex -1;
1265 if (rowIndex < 1 || rowIndex >= rows.size()) { 1290 if (rowIndex < 1 || rowIndex >= this.rows.size()) {
1266 // Out of bounds. 1291 // Out of bounds.
1267 return null; 1292 return null;
1268 } 1293 }
1269 // Interpolate ws. 1294 // Interpolate ws.
1270 Row r1 = rows.get(rowIndex-1); 1295 final Row r1 = this.rows.get(rowIndex-1);
1271 Row r2 = rows.get(rowIndex); 1296 final Row r2 = this.rows.get(rowIndex);
1272 double factor = Linear.factor(km, r1.km, r2.km); 1297 final double factor = Linear.factor(km, r1.km, r2.km);
1273 Linear.weight(factor, r1.ws, r2.ws, result[0]); 1298 Linear.weight(factor, r1.ws, r2.ws, result[0]);
1274 } 1299 }
1275 1300
1276 double [] qs = result[1]; 1301 final double [] qs = result[1];
1277 for (int i = Math.min(qs.length, columns.length)-1; i >= 0; --i) { 1302 for (int i = Math.min(qs.length, this.columns.length)-1; i >= 0; --i) {
1278 qs[i] = columns[i].getQRangeTree().findQ(km); 1303 qs[i] = this.columns[i].getQRangeTree().findQ(km);
1279 } 1304 }
1280 return result; 1305 return result;
1281 } 1306 }
1282 1307
1283 1308
1284 /** True if no QRange is given or Q equals zero. */ 1309 /** True if no QRange is given or Q equals zero. */
1285 public boolean hasEmptyQ() { 1310 public boolean hasEmptyQ() {
1286 for (Column column: columns) { 1311 for (final Column column: this.columns) {
1287 if (column.getQRangeTree() == null) { 1312 if (column.getQRangeTree() == null) {
1288 return true; 1313 return true;
1289 } 1314 }
1290 else { 1315 else {
1291 if (Math.abs(column.getQRangeTree().maxQ()) <= 0.01d) { 1316 if (Math.abs(column.getQRangeTree().maxQ()) <= 0.01d) {
1292 return true; 1317 return true;
1293 } 1318 }
1294 } 1319 }
1295 } 1320 }
1296 1321
1297 if (columns.length == 0) { 1322 if (this.columns.length == 0) {
1298 log.warn("No columns in WstValueTable."); 1323 log.warn("No columns in WstValueTable.");
1299 } 1324 }
1300 1325
1301 return false; 1326 return false;
1302 } 1327 }
1303 1328
1304 1329
1305 /** Find ranges that are between km1 and km2 (inclusive?) */ 1330 /** Find ranges that are between km1 and km2 (inclusive?) */
1306 public List<Range> findSegments(double km1, double km2) { 1331 public List<Range> findSegments(final double km1, final double km2) {
1307 return columns.length != 0 1332 return this.columns.length != 0
1308 ? columns[columns.length-1].getQRangeTree().findSegments(km1, km2) 1333 ? this.columns[this.columns.length-1].getQRangeTree().findSegments(km1, km2)
1309 : Collections.<Range>emptyList(); 1334 : Collections.<Range>emptyList();
1310 } 1335 }
1311 } 1336 }
1312 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : 1337 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org