comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 462:ebf049a1eb53

merged flys-artifacts/2.3.1
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:11 +0200
parents af1b64ec7250
children 929137ee8154
comparison
equal deleted inserted replaced
441:c4c8137e8f0e 462:ebf049a1eb53
1 package de.intevation.flys.artifacts;
2
3 import java.util.List;
4 import java.util.Vector;
5 import java.util.ArrayList;
6
7 import org.w3c.dom.Document;
8 import org.w3c.dom.Element;
9 import org.w3c.dom.Node;
10
11 import org.apache.log4j.Logger;
12
13 import de.intevation.artifacts.ArtifactNamespaceContext;
14 import de.intevation.artifacts.CallContext;
15
16 import de.intevation.artifactdatabase.ProtocolUtils;
17 import de.intevation.artifactdatabase.state.Output;
18 import de.intevation.artifactdatabase.state.State;
19 import de.intevation.artifactdatabase.state.StateEngine;
20 import de.intevation.artifactdatabase.transition.TransitionEngine;
21
22 import de.intevation.artifacts.common.utils.XMLUtils;
23
24 import de.intevation.flys.model.Gauge;
25 import de.intevation.flys.model.River;
26
27 import de.intevation.flys.artifacts.states.DefaultState;
28 import de.intevation.flys.artifacts.context.FLYSContext;
29 import de.intevation.flys.artifacts.math.BackJumpCorrector;
30 import de.intevation.flys.artifacts.model.MainValuesFactory;
31 import de.intevation.flys.artifacts.model.WQCKms;
32 import de.intevation.flys.artifacts.model.WQDay;
33 import de.intevation.flys.artifacts.model.WQKms;
34 import de.intevation.flys.artifacts.model.WstValueTable;
35 import de.intevation.flys.artifacts.model.WstValueTable.QPosition;
36 import de.intevation.flys.artifacts.model.WstValueTableFactory;
37
38 import de.intevation.flys.artifacts.math.LinearRemap;
39
40 import gnu.trove.TDoubleArrayList;
41
42 /**
43 * The default WINFO artifact.
44 *
45 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
46 */
47 public class WINFOArtifact extends FLYSArtifact {
48
49 /** The logger for this class */
50 private static Logger logger = Logger.getLogger(WINFOArtifact.class);
51
52
53 /** The name of the artifact.*/
54 public static final String ARTIFACT_NAME = "winfo";
55
56 /** XPath */
57 public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
58
59
60 /**
61 * The default constructor.
62 */
63 public WINFOArtifact() {
64 }
65
66
67 /**
68 * This method returns a description of this artifact.
69 *
70 * @param data Some data.
71 * @param context The CallContext.
72 *
73 * @return the description of this artifact.
74 */
75 public Document describe(Document data, CallContext context) {
76 logger.debug("Describe: the current state is: " + getCurrentStateId());
77
78 FLYSContext flysContext = null;
79 if (context instanceof FLYSContext) {
80 flysContext = (FLYSContext) context;
81 }
82 else {
83 flysContext = (FLYSContext) context.globalContext();
84 }
85
86 StateEngine stateEngine = (StateEngine) flysContext.get(
87 FLYSContext.STATE_ENGINE_KEY);
88
89 TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
90 FLYSContext.TRANSITION_ENGINE_KEY);
91
92 List<State> reachable = transitionEngine.getReachableStates(
93 this, getCurrentState(context), stateEngine);
94
95 Document description = XMLUtils.newDocument();
96 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
97 description,
98 ArtifactNamespaceContext.NAMESPACE_URI,
99 ArtifactNamespaceContext.NAMESPACE_PREFIX);
100
101 Element root = ProtocolUtils.createRootNode(creator);
102 description.appendChild(root);
103
104 State current = getCurrentState(context);
105
106 ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
107 ProtocolUtils.appendState(creator, root, current);
108 ProtocolUtils.appendReachableStates(creator, root, reachable);
109
110 Element name = ProtocolUtils.createArtNode(
111 creator, "name",
112 new String[] { "value" },
113 new String[] { getName() });
114
115 Element ui = ProtocolUtils.createArtNode(
116 creator, "ui", null, null);
117
118 Element staticUI = ProtocolUtils.createArtNode(
119 creator, "static", null, null);
120
121 Element outs = ProtocolUtils.createArtNode(
122 creator, "outputmodes", null, null);
123 appendOutputModes(description, outs, context, identifier());
124
125 appendStaticUI(description, staticUI, context, identifier());
126
127 Element dynamic = current.describe(
128 this,
129 description,
130 root,
131 context,
132 identifier());
133
134 if (dynamic != null) {
135 ui.appendChild(dynamic);
136 }
137
138 ui.appendChild(staticUI);
139
140 root.appendChild(name);
141 root.appendChild(ui);
142 root.appendChild(outs);
143
144 return description;
145 }
146
147
148 /**
149 * Returns the name of the concrete artifact.
150 *
151 * @return the name of the concrete artifact.
152 */
153 public String getName() {
154 return ARTIFACT_NAME;
155 }
156
157
158 protected void appendOutputModes(
159 Document doc,
160 Element outs,
161 CallContext context,
162 String uuid)
163 {
164 Vector<String> stateIds = getPreviousStateIds();
165
166 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
167 doc,
168 ArtifactNamespaceContext.NAMESPACE_URI,
169 ArtifactNamespaceContext.NAMESPACE_PREFIX);
170
171 FLYSContext flysContext = getFlysContext(context);
172 StateEngine engine = (StateEngine) flysContext.get(
173 FLYSContext.STATE_ENGINE_KEY);
174
175 for (String stateId: stateIds) {
176 logger.debug("Append output modes for state: " + stateId);
177 State state = engine.getState(stateId);
178
179 List<Output> list = state.getOutputs();
180 if (list == null || list.size() == 0) {
181 continue;
182 }
183
184 ProtocolUtils.appendOutputModes(creator, outs, list);
185 }
186
187 try {
188 DefaultState cur = (DefaultState) getCurrentState(context);
189 if (cur.validate(this, context)) {
190 List<Output> list = cur.getOutputs();
191 if (list != null && list.size() > 0) {
192 logger.debug(
193 "Append output modes for state: " + cur.getID());
194
195 ProtocolUtils.appendOutputModes(creator, outs, list);
196 }
197 }
198 }
199 catch (IllegalArgumentException iae) {
200 // state is not valid, so we do not append its outputs.
201 }
202 }
203
204
205 /**
206 * This method appends the static data - that has already been inserted by
207 * the user - to the static node of the DESCRIBE document.
208 *
209 * @param doc The document.
210 * @param ui The root node.
211 * @param context The CallContext.
212 * @param uuid The identifier of the artifact.
213 */
214 protected void appendStaticUI(
215 Document doc,
216 Node ui,
217 CallContext context,
218 String uuid)
219 {
220 Vector<String> stateIds = getPreviousStateIds();
221
222 FLYSContext flysContext = getFlysContext(context);
223 StateEngine engine = (StateEngine) flysContext.get(
224 FLYSContext.STATE_ENGINE_KEY);
225
226 for (String stateId: stateIds) {
227 logger.debug("Append static data for state: " + stateId);
228 DefaultState state = (DefaultState) engine.getState(stateId);
229 state = (DefaultState) fillState(state);
230
231 ui.appendChild(state.describeStatic(doc, ui, context, uuid));
232 }
233 }
234
235
236 //
237 // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
238 //
239
240 /**
241 * Returns the data that is computed by a waterlevel computation.
242 *
243 * @return an array of data triples that consist of W, Q and Kms.
244 */
245 public WQKms[] getWaterlevelData()
246 throws NullPointerException
247 {
248 logger.debug("WINFOArtifact.getWaterlevelData");
249
250 River river = getRiver();
251 if (river == null) {
252 throw new NullPointerException("No river selected.");
253 }
254
255 double[] kms = getKms();
256 if (kms == null) {
257 throw new NullPointerException("No Kms selected.");
258 }
259
260 double[] qs = getQs();
261 double[] ws = null;
262 boolean qSel = true;
263
264 if (qs == null) {
265 logger.debug("Determine Q values based on a set of W values.");
266 qSel = false;
267 ws = getWs();
268 qs = getQsForWs(ws);
269 }
270
271 WstValueTable wst = WstValueTableFactory.getTable(river);
272 if (wst == null) {
273 throw new NullPointerException("No Wst found for selected river.");
274 }
275
276 WQKms[] results = computeWaterlevelData(kms, qs, wst);
277
278 // TODO Introduce a caching mechanism here!
279
280 setWaterlevelNames(results, qSel ? qs : ws, qSel ? "Q" : "W");
281
282 return results;
283 }
284
285
286 /**
287 * Sets the name for waterlevels where each WQKms in <i>r</i> represents a
288 * column.
289 *
290 * @param r The waterlevel columns.
291 * @param v The input values of the computations.
292 * @param wq The WQ mode - can be one of "W" or "Q".
293 */
294 public static void setWaterlevelNames(WQKms[] r, double[] v, String wq) {
295 for (int i = 0; i < v.length; i++) {
296 r[i].setName(wq + "=" + Double.toString(v[i]));
297 }
298 }
299
300
301 /**
302 * Computes the data of a waterlevel computation based on the interpolation
303 * in WstValueTable.
304 *
305 * @param kms The kilometer values.
306 * @param qa The discharge values.
307 * @param wst The WstValueTable used for the interpolation.
308 *
309 * @return an array of data triples that consist of W, Q and Kms.
310 */
311 public static WQKms[] computeWaterlevelData(
312 double[] kms,
313 double[] qs,
314 WstValueTable wst)
315 {
316 logger.info("WINFOArtifact.computeWaterlevelData");
317
318 WQKms[] wqkms = new WQKms[qs.length];
319
320 ArrayList<WQKms> results = new ArrayList<WQKms>();
321
322 for (int i = 0; i < qs.length; i++) {
323 double [] oqs = new double[kms.length];
324 double [] ows = new double[kms.length];
325 int referenceIndex = 0; // TODO: Make depend on the flow direction
326 WstValueTable.QPosition qPosition =
327 wst.interpolate(qs[i], referenceIndex, kms, ows, oqs);
328 if (qPosition != null) {
329 results.add(new WQKms(kms, oqs, ows));
330 }
331 else {
332 logger.warn("interpolation failed for q = " + qs[i]);
333 }
334 }
335
336 return results.toArray(new WQKms[results.size()]);
337 }
338
339
340 /**
341 * Returns the data that is computed by a duration curve computation.
342 *
343 * @return the data computed by a duration curve computation.
344 */
345 public WQDay getDurationCurveData()
346 throws NullPointerException
347 {
348 logger.debug("WINFOArtifact.getDurationCurveData");
349
350 River r = getRiver();
351
352 if (r == null) {
353 throw new NullPointerException("Cannot determine river.");
354 }
355
356 Gauge g = getGauge();
357
358 if (g == null) {
359 throw new NullPointerException("Cannot determine gauge.");
360 }
361
362 double[] locations = getLocations();
363
364 if (locations == null) {
365 throw new NullPointerException("Cannot determine location.");
366 }
367
368 WstValueTable wst = WstValueTableFactory.getTable(r);
369 if (wst == null) {
370 throw new NullPointerException("No Wst found for selected river.");
371 }
372
373 // TODO Introduce a caching mechanism here!
374
375 return computeDurationCurveData(g, wst, locations[0]);
376 }
377
378
379 /**
380 * Computes the data used to create duration curves.
381 *
382 * @param gauge The selected gauge.
383 * @param location The selected location.
384 *
385 * @return the computed data.
386 */
387 public static WQDay computeDurationCurveData(
388 Gauge gauge,
389 WstValueTable wst,
390 double location)
391 {
392 logger.info("WINFOArtifact.computeDurationCurveData");
393
394 Object[] obj = MainValuesFactory.getDurationCurveData(gauge);
395
396 int[] days = (int[]) obj[0];
397 double[] qs = (double[]) obj[1];
398
399 double[] interpolatedW = new double[qs.length];
400 interpolatedW = wst.interpolateW(location, qs, interpolatedW);
401
402 WQDay wqday = new WQDay(qs.length);
403
404 for (int i = 0; i < days.length; i++) {
405 wqday.add(days[i], interpolatedW[i], qs[i]);
406 }
407
408 return wqday;
409 }
410
411
412 /**
413 * Returns the data that is computed by a discharge curve computation.
414 *
415 * @return the data computed by a discharge curve computation.
416 */
417 public WQKms getComputedDischargeCurveData()
418 throws NullPointerException
419 {
420 logger.debug("WINFOArtifact.getComputedDischargeCurveData");
421
422 River r = getRiver();
423
424 if (r == null) {
425 throw new NullPointerException("Cannot determine river.");
426 }
427
428 double[] locations = getLocations();
429
430 if (locations == null) {
431 throw new NullPointerException("Cannot determine location.");
432 }
433
434 WstValueTable wst = WstValueTableFactory.getTable(r);
435 if (wst == null) {
436 throw new NullPointerException("No Wst found for selected river.");
437 }
438
439 WQKms wqkms = computeDischargeCurveData(wst, locations[0]);
440
441 // TODO Introduce a caching mechanism here!
442
443 setComputedDischargeCurveNames(wqkms, locations[0]);
444
445 return wqkms;
446 }
447
448
449 /**
450 * Sets the name of the computed discharge curve data.
451 *
452 * @param wqkms The computed WQKms object.
453 * @param l The location used for the computation.
454 */
455 public static void setComputedDischargeCurveNames(WQKms wqkms, double l) {
456 wqkms.setName(Double.toString(l));
457 }
458
459
460 /**
461 * Computes the data used to create computed discharge curves.
462 *
463 * @param wst The WstValueTable that is used for the interpolation.
464 * @param location The location where the computation should be based on.
465 *
466 * @return an object that contains tuples of W/Q values at the specified
467 * location.
468 */
469 public static WQKms computeDischargeCurveData(
470 WstValueTable wst,
471 double location)
472 {
473 logger.info("WINFOArtifact.computeDischargeCurveData");
474
475 double[][] wqs = wst.interpolateWQ(location);
476
477 if (wqs == null) {
478 logger.error("Cannot compute discharge curve data.");
479 return null;
480 }
481
482 double[] ws = wqs[0];
483 double[] qs = wqs[1];
484
485 WQKms wqkms = new WQKms(ws.length);
486
487 for (int i = 0; i < ws.length; i++) {
488 wqkms.add(ws[i], qs[i], location);
489 }
490
491 return wqkms;
492 }
493
494 /**
495 * Returns the data computed by the discharge longitudinal section
496 * computation.
497 *
498 * @return an array of WQKms object - one object for each given Q value.
499 */
500 public WQKms [] getDischargeLongitudinalSectionData() {
501
502 logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
503
504 River river = getRiver();
505 if (river == null) {
506 logger.error("No river selected.");
507 return new WQKms[0];
508 }
509
510 WstValueTable wst = WstValueTableFactory.getTable(river);
511 if (wst == null) {
512 logger.error("No wst found for selected river.");
513 return new WQKms[0];
514 }
515
516 double [][] segments = getSplittedDistance();
517
518 if (segments.length < 1) {
519 logger.warn("no segments given");
520 return new WQKms[0];
521 }
522
523 if (segments.length == 1) {
524 // fall back to normal "Wasserstand/Wasserspiegellage" calculation
525 double [] qs = toQs(segments[0]);
526 if (qs == null) {
527 logger.warn("no qs given");
528 return new WQKms[0];
529 }
530 if (qs.length == 1) {
531 double [] kms = getKms(segments[0]);
532 return computeWaterlevelData(kms, qs, wst);
533 }
534 }
535
536 // more than one segment
537
538 double [] boundKms;
539
540 if (segments.length == 2) {
541 boundKms = new double [] { segments[0][0], segments[1][1] };
542 }
543 else {
544 TDoubleArrayList bounds = new TDoubleArrayList();
545
546 bounds.add(segments[0][0]);
547
548 for (int i = 1; i < segments.length-1; ++i) {
549 double [] segment = segments[i];
550
551 Gauge gauge = river.determineGauge(segment[0], segment[1]);
552
553 if (gauge == null) {
554 logger.warn("no gauge found between " +
555 segment[0] + " and " + segment[1]);
556 bounds.add(0.5*(segment[0] + segment[1]));
557 }
558 else {
559 bounds.add(gauge.getStation().doubleValue());
560 }
561 }
562
563 bounds.add(segments[segments.length-1][1]);
564 boundKms = bounds.toNativeArray();
565 }
566
567 if (logger.isDebugEnabled()) {
568 logger.debug("bound kms: " + joinDoubles(boundKms));
569 }
570
571 double [][] iqs = null;
572
573 for (int i = 0; i < segments.length; ++i) {
574 double [] iqsi = toQs(segments[i]);
575 if (iqsi == null) {
576 logger.warn("iqsi == null");
577 return new WQKms[0];
578 }
579
580 if (iqs == null) {
581 iqs = new double[iqsi.length][boundKms.length];
582 }
583 else if (iqs.length != iqsi.length) {
584 logger.warn("iqsi.logger != iqs.length: "
585 + iqsi.length + " " + iqsi.length);
586 return new WQKms[0];
587 }
588
589 if (logger.isDebugEnabled()) {
590 logger.debug("segments qs[ " + i + "]: " + joinDoubles(iqsi));
591 }
592
593 for (int j = 0; j < iqs.length; ++j) {
594 iqs[j][i] = iqsi[j];
595 }
596 }
597
598 if (logger.isDebugEnabled()) {
599 for (int i = 0; i < iqs.length; ++i) {
600 logger.debug("iqs[" + i + "]: " + joinDoubles(iqs[i]));
601 }
602 }
603
604 double [] boundWs = new double[boundKms.length];
605 double [] boundQs = new double[boundKms.length];
606
607 double [] okms = getKms(new double [] {
608 boundKms[0], boundKms[boundKms.length-1] });
609
610 ArrayList<WQKms> results = new ArrayList<WQKms>();
611
612 for (int i = 0; i < iqs.length; ++i) {
613 double [] iqsi = iqs[i];
614
615 QPosition qPosition = wst.interpolate(
616 iqsi[0], 0, boundKms, boundWs, boundQs);
617
618 if (qPosition == null) {
619 logger.warn("interpolation failed for " + iqsi[i]);
620 continue;
621 }
622
623 LinearRemap remap = new LinearRemap();
624
625 for (int j = 1; j < boundKms.length; ++j) {
626 remap.add(
627 boundKms[j-1], boundKms[j],
628 boundQs[j-1], iqsi[j-1],
629 boundQs[j], iqsi[j]);
630 }
631
632 double [] oqs = new double[okms.length];
633 double [] ows = new double[okms.length];
634
635 wst.interpolate(okms, ows, oqs, qPosition, remap);
636
637 BackJumpCorrector bjc = new BackJumpCorrector();
638 if (bjc.doCorrection(okms, ows)) {
639 logger.debug("Discharge longitudinal section has backjumps.");
640 results.add(new WQCKms(okms, oqs, ows, bjc.getCorrected()));
641 }
642 else {
643 results.add(new WQKms(okms, oqs, ows));
644 }
645 }
646
647 WQKms [] wqkms = results.toArray(new WQKms[results.size()]);
648
649 setDischargeLongitudinalSectionNames(wqkms, iqs, isQ() ? "Q" : "W");
650
651 return wqkms;
652 }
653
654 protected static String joinDoubles(double [] x) {
655 if (x == null) {
656 return "";
657 }
658 StringBuilder sb = new StringBuilder();
659 for (int i = 0; i < x.length; ++i) {
660 if (i > 0) sb.append(", ");
661 sb.append(x[i]);
662 }
663 return sb.toString();
664 }
665
666 protected double [] toQs(double [] range) {
667 double [] qs = getQs(range);
668 if (qs == null) {
669 logger.debug("Determine Q values based on a set of W values.");
670 double [] ws = getWs(range);
671 qs = getQsForWs(ws);
672 }
673 return qs;
674 }
675
676
677 /**
678 * Sets the name for discharge longitudinal section curves where each WQKms
679 * in <i>r</i> represents a column.
680 */
681 public static void setDischargeLongitudinalSectionNames(
682 WQKms [] wqkms,
683 double [][] iqs,
684 String wq
685 ) {
686 logger.debug("WINFOArtifact.setDischargeLongitudinalSectionNames");
687
688 // TODO: I18N
689
690 for (int j = 0; j < wqkms.length; ++j) {
691 StringBuilder sb = new StringBuilder(wq)
692 .append(" benutzerdefiniert (");
693
694 double [] iqsi = iqs[j];
695 for (int i = 0; i < iqsi.length; i++) {
696 if (i > 0) {
697 sb.append("; ");
698 }
699 sb.append(iqsi[i]);
700 }
701 sb.append(")");
702
703 wqkms[j].setName(sb.toString());
704 }
705 }
706 }
707 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org