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