comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 3818:dc18457b1cef

merged flys-artifacts/pre2.7-2012-03-16
author Thomas Arendsen Hein <thomas@intevation.de>
date Fri, 28 Sep 2012 12:14:59 +0200
parents efb2038783f9
children 3f1cc396d253
comparison
equal deleted inserted replaced
2456:60ab1054069d 3818:dc18457b1cef
1 package de.intevation.flys.artifacts;
2
3 import de.intevation.artifactdatabase.ProtocolUtils;
4
5 import de.intevation.artifactdatabase.data.StateData;
6
7 import de.intevation.artifactdatabase.state.Facet;
8 import de.intevation.artifactdatabase.state.Output;
9 import de.intevation.artifactdatabase.state.State;
10 import de.intevation.artifactdatabase.state.StateEngine;
11
12 import de.intevation.artifactdatabase.transition.TransitionEngine;
13
14 import de.intevation.artifacts.CallContext;
15 import de.intevation.artifacts.Message;
16
17 import de.intevation.artifacts.common.ArtifactNamespaceContext;
18
19 import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
20
21 import de.intevation.artifacts.common.utils.XMLUtils;
22
23 import de.intevation.flys.artifacts.context.FLYSContext;
24
25 import de.intevation.flys.artifacts.model.Calculation1;
26 import de.intevation.flys.artifacts.model.Calculation2;
27 import de.intevation.flys.artifacts.model.Calculation3;
28 import de.intevation.flys.artifacts.model.Calculation4;
29 import de.intevation.flys.artifacts.model.Calculation5;
30 import de.intevation.flys.artifacts.model.Calculation6;
31 import de.intevation.flys.artifacts.model.Calculation;
32 import de.intevation.flys.artifacts.model.CalculationMessage;
33 import de.intevation.flys.artifacts.model.CalculationResult;
34 import de.intevation.flys.artifacts.model.DischargeTables;
35 import de.intevation.flys.artifacts.model.FacetTypes;
36 import de.intevation.flys.artifacts.model.MainValuesFactory;
37 import de.intevation.flys.artifacts.model.Segment;
38 import de.intevation.flys.artifacts.model.WQKms;
39 import de.intevation.flys.artifacts.model.WW;
40 import de.intevation.flys.artifacts.model.WstValueTable;
41 import de.intevation.flys.artifacts.model.WstValueTableFactory;
42
43 import de.intevation.flys.artifacts.states.DefaultState;
44 import de.intevation.flys.artifacts.states.LocationDistanceSelect;
45
46 import de.intevation.flys.geom.Lines;
47
48 import de.intevation.flys.model.FastCrossSectionLine;
49 import de.intevation.flys.model.Gauge;
50 import de.intevation.flys.model.River;
51 import de.intevation.flys.model.DischargeTable;
52
53 import de.intevation.flys.utils.DoubleUtil;
54 import de.intevation.flys.utils.FLYSUtils;
55
56 import gnu.trove.TDoubleArrayList;
57
58 import java.awt.geom.Point2D;
59
60 import java.util.ArrayList;
61 import java.util.Arrays;
62 import java.util.Calendar;
63 import java.util.Collections;
64 import java.util.GregorianCalendar;
65 import java.util.LinkedList;
66 import java.util.List;
67 import java.util.Map;
68
69 import org.apache.log4j.Logger;
70
71 import org.w3c.dom.Document;
72 import org.w3c.dom.Element;
73 import org.w3c.dom.Node;
74
75 /**
76 * The default WINFO artifact.
77 *
78 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
79 */
80 public class WINFOArtifact
81 extends FLYSArtifact
82 implements FacetTypes, WaterLineArtifact {
83
84 /** The logger for this class. */
85 private static Logger logger = Logger.getLogger(WINFOArtifact.class);
86
87 /** The name of the artifact. */
88 public static final String ARTIFACT_NAME = "winfo";
89
90 /** XPath */
91 public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static";
92
93 /** The default number of steps between the start end end of a selected Q
94 * range. */
95 public static final int DEFAULT_Q_STEPS = 30;
96
97 /** The default step width between the start end end kilometer. */
98 public static final double DEFAULT_KM_STEPS = 0.1;
99
100
101 /**
102 * The default constructor.
103 */
104 public WINFOArtifact() {
105 }
106
107
108 /**
109 * This method returns a description of this artifact.
110 *
111 * @param data Some data.
112 * @param context The CallContext.
113 *
114 * @return the description of this artifact.
115 */
116 public Document describe(Document data, CallContext context) {
117 logger.debug("Describe: the current state is: " + getCurrentStateId());
118
119 if (logger.isDebugEnabled()) {
120 dumpArtifact();
121 }
122
123 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
124
125 StateEngine stateEngine = (StateEngine) flysContext.get(
126 FLYSContext.STATE_ENGINE_KEY);
127
128 TransitionEngine transitionEngine = (TransitionEngine) flysContext.get(
129 FLYSContext.TRANSITION_ENGINE_KEY);
130
131 List<State> reachable = transitionEngine.getReachableStates(
132 this, getCurrentState(context), stateEngine);
133
134 Document description = XMLUtils.newDocument();
135 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
136 description,
137 ArtifactNamespaceContext.NAMESPACE_URI,
138 ArtifactNamespaceContext.NAMESPACE_PREFIX);
139
140 Element root = ProtocolUtils.createRootNode(creator);
141 description.appendChild(root);
142
143 State current = getCurrentState(context);
144
145 ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash());
146 ProtocolUtils.appendState(creator, root, current);
147 ProtocolUtils.appendReachableStates(creator, root, reachable);
148
149 appendBackgroundActivity(creator, root, context);
150
151 Element name = ProtocolUtils.createArtNode(
152 creator, "name",
153 new String[] { "value" },
154 new String[] { getName() });
155
156 Element ui = ProtocolUtils.createArtNode(
157 creator, "ui", null, null);
158
159 Element staticUI = ProtocolUtils.createArtNode(
160 creator, "static", null, null);
161
162 Element outs = ProtocolUtils.createArtNode(
163 creator, "outputmodes", null, null);
164 appendOutputModes(description, outs, context, identifier());
165
166 appendStaticUI(description, staticUI, context, identifier());
167
168 Element dynamic = current.describe(
169 this,
170 description,
171 root,
172 context,
173 identifier());
174
175 if (dynamic != null) {
176 ui.appendChild(dynamic);
177 }
178
179 ui.appendChild(staticUI);
180
181 root.appendChild(name);
182 root.appendChild(ui);
183 root.appendChild(outs);
184
185 return description;
186 }
187
188
189 /**
190 * Returns the name of the concrete artifact.
191 *
192 * @return the name of the concrete artifact.
193 */
194 public String getName() {
195 return ARTIFACT_NAME;
196 }
197
198
199 protected static void appendBackgroundActivity(
200 ElementCreator cr,
201 Element root,
202 CallContext context
203 ) {
204 Element inBackground = cr.create("background-processing");
205 root.appendChild(inBackground);
206
207 cr.addAttr(
208 inBackground,
209 "value",
210 String.valueOf(context.isInBackground()),
211 true);
212
213 LinkedList<Message> messages = context.getBackgroundMessages();
214
215 if (messages == null) {
216 return;
217 }
218
219 CalculationMessage message = (CalculationMessage) messages.getLast();
220 cr.addAttr(
221 inBackground,
222 "steps",
223 String.valueOf(message.getSteps()),
224 true);
225
226 cr.addAttr(
227 inBackground,
228 "currentStep",
229 String.valueOf(message.getCurrentStep()),
230 true);
231
232 inBackground.setTextContent(message.getMessage());
233 }
234
235
236 /**
237 * Append output mode nodes to a document.
238 */
239 protected void appendOutputModes(
240 Document doc,
241 Element outs,
242 CallContext context,
243 String uuid)
244 {
245 List<String> stateIds = getPreviousStateIds();
246
247 XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
248 doc,
249 ArtifactNamespaceContext.NAMESPACE_URI,
250 ArtifactNamespaceContext.NAMESPACE_PREFIX);
251
252 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
253 StateEngine engine = (StateEngine) flysContext.get(
254 FLYSContext.STATE_ENGINE_KEY);
255
256 for (String stateId: stateIds) {
257 logger.debug("Append output modes for state: " + stateId);
258 DefaultState state = (DefaultState) engine.getState(stateId);
259
260 List<Output> list = state.getOutputs();
261 if (list == null || list.size() == 0) {
262 logger.debug("-> No output modes for this state.");
263 continue;
264 }
265
266 List<Facet> fs = facets.get(stateId);
267
268 if (fs == null || fs.size() == 0) {
269 logger.debug("No facets for previous state found.");
270 continue;
271 }
272
273 logger.debug("Found " + fs.size() + " facets in previous states.");
274
275 List<Output> generated = generateOutputs(list, fs);
276
277 ProtocolUtils.appendOutputModes(doc, outs, generated);
278 }
279
280 try {
281 DefaultState cur = (DefaultState) getCurrentState(context);
282 if (cur.validate(this)) {
283 List<Output> list = cur.getOutputs();
284 if (list != null && list.size() > 0) {
285 logger.debug(
286 "Append output modes for current state: " + cur.getID());
287
288 List<Facet> fs = facets.get(cur.getID());
289
290 if (fs != null && fs.size() > 0) {
291 List<Output> generated = generateOutputs(list, fs);
292
293 logger.debug("Found " + fs.size() + " current facets.");
294 if (!generated.isEmpty()) {
295 ProtocolUtils.appendOutputModes(
296 doc, outs, generated);
297 }
298 }
299 else {
300 logger.debug("No facets found for the current state.");
301 }
302 }
303 }
304 }
305 catch (IllegalArgumentException iae) {
306 // state is not valid, so we do not append its outputs.
307 }
308 }
309
310
311 /**
312 * This method appends the static data - that has already been inserted by
313 * the user - to the static node of the DESCRIBE document.
314 *
315 * @param doc The document.
316 * @param ui The root node.
317 * @param context The CallContext.
318 * @param uuid The identifier of the artifact.
319 */
320 protected void appendStaticUI(
321 Document doc,
322 Node ui,
323 CallContext context,
324 String uuid)
325 {
326 List<String> stateIds = getPreviousStateIds();
327
328 FLYSContext flysContext = FLYSUtils.getFlysContext(context);
329 StateEngine engine = (StateEngine) flysContext.get(
330 FLYSContext.STATE_ENGINE_KEY);
331
332 for (String stateId: stateIds) {
333 logger.debug("Append static data for state: " + stateId);
334 DefaultState state = (DefaultState) engine.getState(stateId);
335
336 ui.appendChild(state.describeStatic(this, doc, ui, context, uuid));
337 }
338 }
339
340
341 //
342 // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES
343 //
344
345 /**
346 * Returns the data that is computed by a waterlevel computation.
347 *
348 * @return an array of data triples that consist of W, Q and Kms.
349 */
350 public CalculationResult getWaterlevelData()
351 {
352 logger.debug("WINFOArtifact.getWaterlevelData");
353
354 River river = FLYSUtils.getRiver(this);
355 if (river == null) {
356 return error(new WQKms[0], "no.river.selected");
357 }
358
359 double[] kms = getKms();
360 if (kms == null) {
361 return error(new WQKms[0], "no.kms.selected");
362 }
363
364 double[] qs = getQs();
365 double[] ws = null;
366 boolean qSel = true;
367
368 if (qs == null) {
369 logger.debug("Determine Q values based on a set of W values.");
370 qSel = false;
371 ws = getWs();
372 double [][] qws = getQsForWs(ws);
373 if (qws == null || qws.length == 0) {
374 return error(new WQKms[0], "converting.ws.to.qs.failed");
375 }
376 qs = qws[0];
377
378 if (qws[1] != null) { // If new ws where generated.
379 // TODO: Inform user!
380 ws = qws[1];
381 }
382 }
383
384 WstValueTable wst = WstValueTableFactory.getTable(river);
385 if (wst == null) {
386 return error(new WQKms[0], "no.wst.for.selected.river");
387 }
388
389
390 double [] range = FLYSUtils.getKmRange(this);
391 if (range == null) {
392 return error(new WQKms[0], "no.range.found");
393 }
394
395 double refKm;
396
397 if (isFreeQ() || isFreeW()) {
398 refKm = range[0];
399 logger.debug("'free' calculation (km " + refKm + ")");
400 }
401 else {
402 Gauge gauge = river.determineGaugeByPosition(range[0]);
403 if (gauge == null) {
404 return error(
405 new WQKms[0], "no.gauge.found.for.km");
406 }
407
408 refKm = gauge.getStation().doubleValue();
409
410 logger.debug(
411 "reference gauge: " + gauge.getName() + " (km " + refKm + ")");
412 }
413
414 return computeWaterlevelData(kms, qs, ws, wst, refKm);
415 }
416
417
418 /**
419 * Computes the data of a waterlevel computation based on the interpolation
420 * in WstValueTable.
421 *
422 * @param kms The kilometer values.
423 * @param qa The discharge values.
424 * @param wst The WstValueTable used for the interpolation.
425 *
426 * @return an array of data triples that consist of W, Q and Kms.
427 */
428 public static CalculationResult computeWaterlevelData(
429 double [] kms,
430 double [] qs,
431 double [] ws,
432 WstValueTable wst,
433 double refKm
434 ) {
435 logger.info("WINFOArtifact.computeWaterlevelData");
436
437 Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm);
438
439 return calc1.calculate(wst);
440 }
441
442
443 /**
444 * Returns the data that is computed by a duration curve computation.
445 *
446 * @return the data computed by a duration curve computation.
447 */
448 public CalculationResult getDurationCurveData() {
449 logger.debug("WINFOArtifact.getDurationCurveData");
450
451 River r = FLYSUtils.getRiver(this);
452
453 if (r == null) {
454 return error(null, "no.river.selected");
455 }
456
457 Gauge g = getGauge();
458
459 if (g == null) {
460 return error(null, "no.gauge.selected");
461 }
462
463 double[] locations = FLYSUtils.getLocations(this);
464
465 if (locations == null) {
466 return error(null, "no.locations.selected");
467 }
468
469 WstValueTable wst = WstValueTableFactory.getTable(r);
470 if (wst == null) {
471 return error(null, "no.wst.for.river");
472 }
473
474 return computeDurationCurveData(g, wst, locations[0]);
475 }
476
477
478 /**
479 * Computes the data used to create duration curves.
480 *
481 * @param gauge The selected gauge.
482 * @param location The selected location.
483 *
484 * @return the computed data.
485 */
486 public static CalculationResult computeDurationCurveData(
487 Gauge gauge,
488 WstValueTable wst,
489 double location)
490 {
491 logger.info("WINFOArtifact.computeDurationCurveData");
492
493 Object[] obj = MainValuesFactory.getDurationCurveData(gauge);
494
495 int[] days = (int[]) obj[0];
496 double[] qs = (double[]) obj[1];
497
498 Calculation3 calculation = new Calculation3(location, days, qs);
499
500 return calculation.calculate(wst);
501 }
502
503
504 /**
505 * Returns the data that is used to create discharge curves.
506 *
507 */
508 public CalculationResult getDischargeCurveData() {
509
510 River river = FLYSUtils.getRiver(this);
511 if (river == null) {
512 return error(new WQKms[0], "no.river.selected");
513 }
514
515 double [] distance = FLYSUtils.getKmRange(this);
516
517 if (distance == null) {
518 return error(new WQKms[0], "no.range.found");
519 }
520
521 List<Gauge> gauges = river.determineGauges(distance[0], distance[1]);
522
523 if (gauges.isEmpty()) {
524 return error(new WQKms[0], "no.gauge.selected");
525 }
526
527 String [] names = new String[gauges.size()];
528
529 for (int i = 0; i < names.length; ++i) {
530 names[i] = gauges.get(i).getName();
531 }
532
533 DischargeTables dt = new DischargeTables(river.getName(), names);
534
535 Map<String, double [][]> map = dt.getValues(100d);
536
537 ArrayList<WQKms> res = new ArrayList<WQKms>();
538
539 for (Gauge gauge: gauges) {
540 String name = gauge.getName();
541 double [][] values = map.get(name);
542 if (values == null) {
543 continue;
544 }
545 double [] kms = new double[values[0].length];
546 Arrays.fill(kms, gauge.getStation().doubleValue());
547 res.add(new WQKms(kms, values[0], values[1], name));
548 }
549
550 return new CalculationResult(
551 res.toArray(new WQKms[res.size()]),
552 new Calculation());
553 }
554
555
556 /**
557 * Returns the data that is computed by a discharge curve computation.
558 *
559 * @return the data computed by a discharge curve computation.
560 */
561 public CalculationResult getComputedDischargeCurveData()
562 throws NullPointerException
563 {
564 logger.debug("WINFOArtifact.getComputedDischargeCurveData");
565
566 River r = FLYSUtils.getRiver(this);
567
568 if (r == null) {
569 return error(new WQKms[0], "no.river.selected");
570 }
571
572 double[] locations = FLYSUtils.getLocations(this);
573
574 if (locations == null) {
575 return error(new WQKms[0], "no.locations.selected");
576 }
577
578 WstValueTable wst = WstValueTableFactory.getTable(r);
579 if (wst == null) {
580 return error(new WQKms[0], "no.wst.for.river");
581 }
582
583 return computeDischargeCurveData(wst, locations[0]);
584 }
585
586
587 /**
588 * Computes the data used to create computed discharge curves.
589 *
590 * @param wst The WstValueTable that is used for the interpolation.
591 * @param location The location where the computation should be based on.
592 *
593 * @return an object that contains tuples of W/Q values at the specified
594 * location.
595 */
596 public static CalculationResult computeDischargeCurveData(
597 WstValueTable wst,
598 double location)
599 {
600 logger.info("WINFOArtifact.computeDischargeCurveData");
601
602 Calculation2 calculation = new Calculation2(location);
603
604 return calculation.calculate(wst);
605 }
606
607 protected static final CalculationResult error(Object data, String msg) {
608 return new CalculationResult(data, new Calculation(msg));
609 }
610
611
612 /**
613 * Returns the data computed by the discharge longitudinal section
614 * computation.
615 *
616 * @return an array of WQKms object - one object for each given Q value.
617 */
618 public CalculationResult getDischargeLongitudinalSectionData() {
619
620 logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData");
621
622 River river = FLYSUtils.getRiver(this);
623 if (river == null) {
624 logger.debug("No river selected.");
625 return error(new WQKms[0], "no.river.selected");
626 }
627
628 WstValueTable table = WstValueTableFactory.getTable(river);
629 if (table == null) {
630 logger.debug("No wst found for selected river.");
631 return error(new WQKms[0], "no.wst.for.river");
632 }
633
634 List<Segment> segments = getSegments();
635
636 if (segments == null) {
637 logger.debug("Cannot create segments.");
638 return error(new WQKms[0], "cannot.create.segments");
639 }
640
641 double [] range = getFromToStep();
642
643 if (range == null) {
644 logger.debug("Cannot figure out range.");
645 return error(new WQKms[0], "no.range.found");
646 }
647
648 Calculation4 calc4 = new Calculation4(segments, river, isQ());
649
650 return calc4.calculate(table, range[0], range[1], range[2]);
651 }
652
653
654 /**
655 * Returns the data that is computed by a reference curve computation.
656 *
657 * @return the data computed by a reference curve computation.
658 */
659 public CalculationResult getReferenceCurveData(CallContext context) {
660
661 Double startKm = getReferenceStartKm();
662
663 if (startKm == null) {
664 return error(new WW[0], "no.reference.start.km");
665 }
666
667 double [] endKms = getReferenceEndKms();
668
669 if (endKms == null || endKms.length == 0) {
670 return error(new WW[0], "no.reference.end.kms");
671 }
672
673 Calculation5 calc5 = new Calculation5(startKm, endKms);
674
675 River r = FLYSUtils.getRiver(this);
676 if (r == null) {
677 return error(new WW[0], "no.river.found");
678 }
679
680 WstValueTable wst = WstValueTableFactory.getTable(r);
681 if (wst == null) {
682 return error(new WW[0], "no.wst.for.river");
683 }
684
685 Map<Double, Double> kms2gaugeDatums = r.queryGaugeDatumsKMs();
686
687 return calc5.calculate(wst, kms2gaugeDatums, context);
688 }
689
690
691 /** Get reference (start) km. */
692 protected Double getReferenceStartKm() {
693 StateData sd = getData("reference_startpoint");
694
695 if (sd == null) {
696 logger.warn("no reference start given.");
697 return null;
698 }
699
700 logger.debug("Reference start km given: " + sd.getValue());
701
702 String input = (String) sd.getValue();
703
704 if (input == null | (input = input.trim()).length() == 0) {
705 logger.warn("reference start string is empty.");
706 return null;
707 }
708
709 try {
710 return Double.valueOf(input);
711 }
712 catch (NumberFormatException nfe) {
713 logger.warn("reference start string is not numeric.");
714 }
715
716 return null;
717 }
718
719
720 protected double [] getReferenceEndKms() {
721 StateData sd = getData("reference_endpoint");
722
723 if (sd == null) {
724 logger.warn("no reference end given.");
725 return null;
726 }
727 else {
728 logger.debug("Reference end km : " + sd.getValue());
729 }
730
731 String input = (String) sd.getValue();
732
733 if (input == null || (input = input.trim()).length() == 0) {
734 logger.warn("reference end string is empty.");
735 return null;
736 }
737
738 TDoubleArrayList endKms = new TDoubleArrayList();
739
740 for (String part: input.split("\\s+")) {
741 try {
742 double km = Double.parseDouble(part);
743 if (!endKms.contains(km)) {
744 endKms.add(km);
745 }
746 }
747 catch (NumberFormatException nfe) {
748 logger.warn("reference end string is not numeric.");
749 }
750 }
751
752 return endKms.toNativeArray();
753 }
754
755
756 public CalculationResult getHistoricalDischargeData() {
757 Gauge gauge = FLYSUtils.getReferenceGauge(this);
758 String rawTimerange = getDataAsString("year_range");
759 String rawValues = getDataAsString("historical_values");
760 int mode = getDataAsInteger("historical_mode");
761
762 int[] timerange = FLYSUtils.intArrayFromString(rawTimerange);
763 double[] values = FLYSUtils.doubleArrayFromString(rawValues);
764
765 Calendar start = new GregorianCalendar(timerange[0], 0, 1);
766 Calendar end = new GregorianCalendar(timerange[1], 0, 1);
767
768 Calculation6 calc = new Calculation6(
769 mode,
770 new long[] { start.getTimeInMillis(), end.getTimeInMillis() },
771 values);
772
773 return calc.calculate(gauge);
774 }
775
776
777 public List<Segment> getSegments() {
778 StateData wqValues = getData("wq_values");
779 if (wqValues == null) {
780 logger.warn("no wq_values given");
781 return Collections.emptyList();
782 }
783 String input = (String)wqValues.getValue();
784 if (input == null || (input = input.trim()).length() == 0) {
785 logger.warn("wq_values are empty");
786 return Collections.emptyList();
787 }
788 return Segment.parseSegments(input);
789 }
790
791
792 /**
793 * Get points of line describing the surface of water at cross section.
794 *
795 * @return an array holding coordinates of points of surface of water (
796 * in the form {{x1, x2} {y1, y2}} ).
797 */
798 public double [][] getWaterLines(int idx, FastCrossSectionLine csl) {
799 logger.debug("getWaterLines(" + idx + ")");
800
801 List<Point2D> points = csl.getPoints();
802
803 // Need W at km
804 WQKms [] wqkms = (WQKms[]) getWaterlevelData().getData();
805 if (wqkms.length == 0) {
806 logger.error("No WQKms found.");
807 return Lines.createWaterLines(points, 0.0f);
808 }
809
810 if (wqkms.length < idx) {
811 logger.error("getWaterLines() requested index ("
812 + idx + " not found.");
813 }
814
815 // Find W at km, linear naive approach.
816 WQKms triple = wqkms[idx];
817
818 // Find index of km.
819 double wishKM = csl.getKm();
820 int old_idx = 0;
821
822 if (triple.size() == 0) {
823 logger.warn("Calculation of waterline is empty.");
824 return Lines.createWaterLines(points, 0.0f);
825 }
826
827 // Linear seach in WQKms for closest km.
828 double old_dist_wish = Math.abs(wishKM - triple.getKm(0));
829 double last_w = triple.getW(0);
830
831 for (int i = 0, T = triple.size(); i < T; i++) {
832 double diff = Math.abs(wishKM - triple.getKm(i));
833 if (diff > old_dist_wish) {
834 break;
835 }
836 last_w = triple.getW(i);
837 old_dist_wish = diff;
838 }
839 return Lines.createWaterLines(points, last_w);
840 }
841
842
843 /**
844 * Returns the Qs for a number of Ws. This method makes use of
845 * DischargeTables.getQForW().
846 *
847 * @param ws An array of W values.
848 *
849 * @return an array of Q values.
850 */
851 public double [][] getQsForWs(double[] ws) {
852
853 if (ws == null) {
854 logger.error("getQsForWs: ws == null");
855 return null;
856 }
857
858 boolean debug = logger.isDebugEnabled();
859
860 if (debug) {
861 logger.debug("FLYSArtifact.getQsForWs");
862 }
863
864 River r = FLYSUtils.getRiver(this);
865 if (r == null) {
866 logger.warn("no river found");
867 return null;
868 }
869
870 double [] range = FLYSUtils.getKmRange(this);
871 if (range == null) {
872 logger.warn("no ranges found");
873 return null;
874 }
875
876 if (isFreeW()) {
877 logger.debug("Bezugslinienverfahren I: W auf freier Strecke");
878 // The simple case of the "Bezugslinienverfahren"
879 // "W auf freier Strecke".
880 WstValueTable wst = WstValueTableFactory.getTable(r);
881 if (wst == null) {
882 logger.warn("no wst value table found");
883 return null;
884 }
885 double km = range[0];
886
887 TDoubleArrayList outQs = new TDoubleArrayList(ws.length);
888 TDoubleArrayList outWs = new TDoubleArrayList(ws.length);
889
890 boolean generatedWs = false;
891
892 for (int i = 0; i < ws.length; ++i) {
893 double w = ws[i];
894 if (debug) {
895 logger.debug("getQsForWs: lookup Q for W: " + w);
896 }
897 // There could bemore than one Q per W.
898 double [] qs = wst.findQsForW(km, w);
899 for (int j = 0; j < qs.length; ++j) {
900 outWs.add(ws[i]);
901 outQs.add(qs[j]);
902 }
903 generatedWs |= qs.length != 1;
904 }
905
906 if (debug) {
907 logger.debug("getQsForWs: number of Qs: " + outQs.size());
908 }
909
910 return new double [][] {
911 outQs.toNativeArray(),
912 generatedWs ? outWs.toNativeArray() : null };
913 }
914
915 if (debug) {
916 logger.debug("range: " + Arrays.toString(range));
917 }
918
919 Gauge g = r.determineGaugeByPosition(range[0]);
920 if (g == null) {
921 logger.warn("no gauge found for km: " + range[0]);
922 return null;
923 }
924
925 if (debug) {
926 logger.debug("convert w->q with gauge '" + g.getName() + "'");
927 }
928
929 DischargeTable dt = g.fetchMasterDischargeTable();
930
931 if (dt == null) {
932 logger.warn("No master discharge table found for gauge '"
933 + g.getName() + "'");
934 return null;
935 }
936
937 double [][] values = DischargeTables.loadDischargeTableValues(dt, 1);
938
939 TDoubleArrayList wsOut = new TDoubleArrayList(ws.length);
940 TDoubleArrayList qsOut = new TDoubleArrayList(ws.length);
941
942 boolean generatedWs = false;
943
944 for (int i = 0; i < ws.length; i++) {
945 if (Double.isNaN(ws[i])) {
946 logger.warn("W is NaN: ignored");
947 continue;
948 }
949 double w = ws[i] / 100d;
950 double [] qs = DischargeTables.getQsForW(values, w);
951
952 if (qs.length == 0) {
953 logger.warn("No Qs found for W = " + ws[i]);
954 }
955 else {
956 for (double q: qs) {
957 wsOut.add(ws[i]);
958 qsOut.add(q);
959 }
960 }
961 generatedWs |= qs.length != 1;
962 }
963
964 return new double [][] {
965 qsOut.toNativeArray(),
966 generatedWs ? wsOut.toNativeArray() : null
967 };
968 }
969
970
971 /**
972 * Determines the selected mode of distance/range input.
973 *
974 * @return true, if the range mode is selected otherwise false.
975 */
976 public boolean isRange() {
977 StateData mode = getData("ld_mode");
978
979 if (mode == null) {
980 logger.warn("No mode location/range chosen. Defaults to range.");
981 return true;
982 }
983
984 String value = (String) mode.getValue();
985
986 return value.equals("distance");
987 }
988
989
990 /**
991 * Returns the selected distance based on a given range (from, to).
992 *
993 * @param dFrom The StateData that contains the lower value.
994 * @param dTo The StateData that contains the upper value.
995 *
996 * @return the selected distance.
997 */
998 protected double[] getDistanceByRange(StateData dFrom, StateData dTo) {
999 double from = Double.parseDouble((String) dFrom.getValue());
1000 double to = Double.parseDouble((String) dTo.getValue());
1001
1002 return new double[] { from, to };
1003 }
1004
1005
1006 /**
1007 * Returns the selected Kms.
1008 *
1009 * @param distance An 2dim array with [lower, upper] values.
1010 *
1011 * @return the selected Kms.
1012 */
1013 public double[] getKms(double[] distance) {
1014 StateData dStep = getData("ld_step");
1015
1016 if (dStep == null) {
1017 logger.warn("No step width given. Cannot compute Kms.");
1018 return null;
1019 }
1020
1021 double step = Double.parseDouble((String) dStep.getValue());
1022
1023 // transform step from 'm' into 'km'
1024 step = step / 1000;
1025
1026 if (step == 0d) {
1027 step = DEFAULT_KM_STEPS;
1028 }
1029
1030 return DoubleUtil.explode(distance[0], distance[1], step);
1031 }
1032
1033
1034 /**
1035 * Returns the selected Kms.
1036 *
1037 * @return the selected kms.
1038 */
1039 public double[] getKms() {
1040 if (isRange()) {
1041 double[] distance = FLYSUtils.getKmRange(this);
1042 return getKms(distance);
1043
1044 }
1045 else {
1046 return LocationDistanceSelect.getLocations(this);
1047 }
1048 }
1049
1050
1051 public double [] getFromToStep() {
1052 if (!isRange()) {
1053 return null;
1054 }
1055 double [] fromTo = FLYSUtils.getKmRange(this);
1056
1057 if (fromTo == null) {
1058 return null;
1059 }
1060
1061 StateData dStep = getData("ld_step");
1062 if (dStep == null) {
1063 return null;
1064 }
1065
1066 double [] result = new double[3];
1067 result[0] = fromTo[0];
1068 result[1] = fromTo[1];
1069
1070 try {
1071 String step = (String)dStep.getValue();
1072 result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d);
1073 }
1074 catch (NumberFormatException nfe) {
1075 return null;
1076 }
1077
1078 return result;
1079 }
1080
1081
1082 /**
1083 * Returns the gauge based on the current distance and river.
1084 *
1085 * @return the gauge.
1086 */
1087 public Gauge getGauge() {
1088 return FLYSUtils.getGauge(this);
1089 }
1090
1091
1092 /**
1093 * Returns the gauges that match the selected kilometer range.
1094 *
1095 * @return the gauges based on the selected kilometer range.
1096 */
1097 public List<Gauge> getGauges() {
1098
1099 River river = FLYSUtils.getRiver(this);
1100 if (river == null) {
1101 return null;
1102 }
1103
1104 double [] dist = FLYSUtils.getKmRange(this);
1105 if (dist == null) {
1106 return null;
1107 }
1108
1109 return river.determineGauges(dist[0], dist[1]);
1110 }
1111
1112
1113 /**
1114 * This method returns the Q values.
1115 *
1116 * @return the selected Q values or null, if no Q values are selected.
1117 */
1118 public double[] getQs() {
1119 StateData dMode = getData("wq_isq");
1120 StateData dSelection = getData("wq_isrange");
1121
1122 boolean isRange = dSelection != null
1123 ? Boolean.valueOf((String)dSelection.getValue())
1124 : false;
1125
1126 if (isQ()) {
1127 if (!isRange) {
1128 return getSingleWQValues();
1129 }
1130 else {
1131 return getWQTriple();
1132 }
1133 }
1134 else {
1135 logger.warn("You try to get Qs, but W has been inserted.");
1136 return null;
1137 }
1138 }
1139
1140
1141 public boolean isQ() {
1142 StateData mode = getData("wq_isq");
1143 String value = (mode != null) ? (String) mode.getValue() : null;
1144 return value != null ? Boolean.valueOf(value) : false;
1145 }
1146
1147 public boolean isW() {
1148 StateData mode = getData("wq_isq");
1149 String value = (mode != null) ? (String) mode.getValue() : null;
1150 return value != null ? !Boolean.valueOf(value) : false;
1151 }
1152
1153 public boolean isFreeW() {
1154 if(!isW()) {
1155 return false;
1156 }
1157 StateData mode = getData("wq_isfree");
1158 String value = (mode != null) ? (String) mode.getValue() : null;
1159
1160 return value != null ? Boolean.valueOf(value) : false;
1161 }
1162
1163
1164 /**
1165 * Returns true, if the parameter is set to compute data on a free range.
1166 * Otherwise it returns false, which tells the calculation that it is bound
1167 * to a gauge.
1168 *
1169 * @return true, if the calculation should compute on a free range otherwise
1170 * false and the calculation is bound to a gauge.
1171 */
1172 public boolean isFreeQ() {
1173 if(!isQ()) {
1174 return false;
1175 }
1176 StateData mode = getData("wq_isfree");
1177 String value = (mode != null) ? (String) mode.getValue() : null;
1178
1179 logger.debug("isFreeQ: " + value);
1180
1181 return value != null && Boolean.valueOf(value);
1182 }
1183
1184
1185 /**
1186 * Returns the Q values based on a specified kilometer range.
1187 *
1188 * @param range A 2dim array with lower and upper kilometer range.
1189 *
1190 * @return an array of Q values.
1191 */
1192 public double[] getQs(double[] range) {
1193 StateData dMode = getData("wq_isq");
1194
1195 if (isQ()) {
1196 return getWQForDist(range);
1197 }
1198
1199 logger.warn("You try to get Qs, but Ws has been inserted.");
1200 return null;
1201 }
1202
1203
1204 /**
1205 * Returns the W values based on a specified kilometer range.
1206 *
1207 * @param range A 2dim array with lower and upper kilometer range.
1208 *
1209 * @return an array of W values.
1210 */
1211 public double[] getWs(double[] range) {
1212 if (isW()) {
1213 return getWQForDist(range);
1214 }
1215
1216 logger.warn("You try to get Ws, but Qs has been inserted.");
1217 return null;
1218 }
1219
1220
1221 /**
1222 * This method returns the W values.
1223 *
1224 * @return the selected W values or null, if no W values are selected.
1225 */
1226 public double[] getWs() {
1227 StateData dSingle = getData("wq_single");
1228
1229 if (isW()) {
1230 if (dSingle != null) {
1231 return getSingleWQValues();
1232 }
1233 else {
1234 return getWQTriple();
1235 }
1236 }
1237 else {
1238 logger.warn("You try to get Ws, but Q has been inserted.");
1239 return null;
1240 }
1241 }
1242
1243 /**
1244 * This method returns the given W or Q values for a specific range
1245 * (inserted in the WQ input panel for discharge longitudinal sections).
1246 *
1247 * @param dist A 2dim array with lower und upper kilometer values.
1248 *
1249 * @return an array of W or Q values.
1250 */
1251 protected double[] getWQForDist(double[] dist) {
1252 logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
1253 StateData data = getData("wq_values");
1254
1255 if (data == null) {
1256 logger.warn("Missing wq values!");
1257 return null;
1258 }
1259
1260 String dataString = (String) data.getValue();
1261 String[] ranges = dataString.split(":");
1262
1263 for (String range: ranges) {
1264 String[] parts = range.split(";");
1265
1266 double lower = Double.parseDouble(parts[0]);
1267 double upper = Double.parseDouble(parts[1]);
1268
1269 if (lower <= dist[0] && upper >= dist[1]) {
1270 String[] values = parts[2].split(",");
1271
1272 int num = values.length;
1273 double[] res = new double[num];
1274
1275 for (int i = 0; i < num; i++) {
1276 try {
1277 res[i] = Double.parseDouble(values[i]);
1278 }
1279 catch (NumberFormatException nfe) {
1280 logger.warn(nfe, nfe);
1281 }
1282 }
1283
1284 return res;
1285 }
1286 }
1287
1288 logger.warn("Specified range for WQ not found!");
1289
1290 return null;
1291 }
1292
1293
1294 /**
1295 * This method returns an array of inserted WQ triples that consist of from,
1296 * to and the step width.
1297 *
1298 * @return an array of from, to and step width.
1299 */
1300 protected double[] getWQTriple() {
1301 StateData dFrom = getData("wq_from");
1302 StateData dTo = getData("wq_to");
1303
1304 if (dFrom == null || dTo == null) {
1305 logger.warn("Missing start or end value for range.");
1306 return null;
1307 }
1308
1309 double from = Double.parseDouble((String) dFrom.getValue());
1310 double to = Double.parseDouble((String) dTo.getValue());
1311
1312 StateData dStep = getData("wq_step");
1313
1314 if (dStep == null) {
1315 logger.warn("No step width given. Cannot compute Qs.");
1316 return null;
1317 }
1318
1319 double step = Double.parseDouble((String) dStep.getValue());
1320
1321 // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
1322 // width. Maybe, we should round the value to a number of digits.
1323 if (step == 0d) {
1324 double diff = to - from;
1325 step = diff / DEFAULT_Q_STEPS;
1326 }
1327
1328 return DoubleUtil.explode(from, to, step);
1329 }
1330
1331
1332 /**
1333 * Returns an array of inserted WQ double values stored as whitespace
1334 * separated list.
1335 *
1336 * @return an array of W or Q values.
1337 */
1338 protected double[] getSingleWQValues() {
1339 StateData dSingle = getData("wq_single");
1340
1341 if (dSingle == null) {
1342 logger.warn("Cannot determine single WQ values. No data given.");
1343 return null;
1344 }
1345
1346 String tmp = (String) dSingle.getValue();
1347 String[] strValues = tmp.split(" ");
1348
1349 TDoubleArrayList values = new TDoubleArrayList();
1350
1351 for (String strValue: strValues) {
1352 try {
1353 values.add(Double.parseDouble(strValue));
1354 }
1355 catch (NumberFormatException nfe) {
1356 logger.warn(nfe, nfe);
1357 }
1358 }
1359
1360 values.sort();
1361
1362 return values.toNativeArray();
1363 }
1364
1365
1366 /**
1367 * Determines Facets initial disposition regarding activity (think of
1368 * selection in Client ThemeList GUI). This will be checked one time
1369 * when the facet enters a collections describe document.
1370 *
1371 * @param facetName name of the facet.
1372 * @param index index of the facet.
1373 * @return 0 if not active
1374 */
1375 @Override
1376 public int getInitialFacetActivity(String outputName, String facetName, int index) {
1377 String [] inactives = new String[] {
1378 LONGITUDINAL_Q,
1379 DURATION_Q
1380 };
1381
1382 logger.debug("WINFOArtifact.active?: "
1383 + outputName
1384 + "/"
1385 + facetName);
1386
1387 if (facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_Q) ||
1388 facetName.equals(COMPUTED_DISCHARGE_MAINVALUES_W)
1389 && outputName.equals("computed_discharge_curve"))
1390 {
1391 return 0;
1392 }
1393 return Arrays.asList(inactives).contains(facetName)
1394 ? 0
1395 : 1;
1396 }
1397 }
1398 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org