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