Mercurial > dive4elements > gnv-client
comparison gnv-artifacts/src/main/java/de/intevation/gnv/chart/HorizontalProfileChart.java @ 1119:7c4f81f74c47
merged gnv-artifacts
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:00 +0200 |
parents | f953c9a559d8 |
children |
comparison
equal
deleted
inserted
replaced
1027:fca4b5eb8d2f | 1119:7c4f81f74c47 |
---|---|
1 /* | |
2 * Copyright (c) 2010 by Intevation GmbH | |
3 * | |
4 * This program is free software under the LGPL (>=v2.1) | |
5 * Read the file LGPL.txt coming with the software for details | |
6 * or visit http://www.gnu.org/licenses/ if it does not exist. | |
7 */ | |
8 | |
9 package de.intevation.gnv.chart; | |
10 | |
11 import com.vividsolutions.jts.geom.Point; | |
12 | |
13 import com.vividsolutions.jts.io.ParseException; | |
14 import com.vividsolutions.jts.io.WKTReader; | |
15 | |
16 import de.intevation.gnv.geobackend.base.Result; | |
17 | |
18 import de.intevation.gnv.utils.DistanceCalculator; | |
19 | |
20 import java.util.Collection; | |
21 import java.util.Locale; | |
22 import java.util.Map; | |
23 | |
24 import org.apache.log4j.Logger; | |
25 | |
26 import org.jfree.chart.ChartTheme; | |
27 | |
28 import org.jfree.chart.axis.Axis; | |
29 import org.jfree.chart.axis.AxisLocation; | |
30 import org.jfree.chart.axis.NumberAxis; | |
31 import org.jfree.chart.axis.NumberTickUnit; | |
32 | |
33 import org.jfree.chart.plot.PlotOrientation; | |
34 import org.jfree.chart.plot.XYPlot; | |
35 | |
36 import org.jfree.data.Range; | |
37 | |
38 import org.jfree.data.general.Series; | |
39 | |
40 import org.jfree.data.xy.XYSeries; | |
41 | |
42 /** | |
43 * This class is used to create xy-charts of horizontal profiles. | |
44 * | |
45 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> | |
46 */ | |
47 public class HorizontalProfileChart | |
48 extends VerticalProfileChart | |
49 { | |
50 /** | |
51 * Logger used for logging with log4j. | |
52 */ | |
53 private static Logger log = Logger.getLogger(HorizontalProfileChart.class); | |
54 | |
55 /** | |
56 * <code>WKTReader</code> used to turn wkt strings into geometries. | |
57 */ | |
58 private static WKTReader wktReader = new WKTReader(); | |
59 | |
60 /** | |
61 * The first point in a HorizontalProfileChart. It is used to calculate the | |
62 * distance between the currently processed point an the start. | |
63 */ | |
64 protected Point lastPoint; | |
65 | |
66 protected double distance = 0d; | |
67 | |
68 /** | |
69 * Constructor used to create horizontal profile charts. | |
70 * | |
71 * @param labels Labels used to be displayed in title, subtitle and so on. | |
72 * @param theme ChartTheme used to adjust the rendering of this chart. | |
73 * @param parameters Collection containing a bunch of parameters. | |
74 * @param measurements Collection containing a bunch of measurements. | |
75 * @param dates Collection containing a bunch of date objects. | |
76 * @param result Collection containing a bunch of <code>Result</code> | |
77 * objects which contain the actual data items to be displayed. | |
78 * @param timeGaps Collection with timegap definitions. | |
79 * @param locale Locale used to specify the format of labels, numbers, ... | |
80 * @param linesVisible Render lines between data points if true, otherwise | |
81 * not. | |
82 * @param shapesVisible Render vertices as points if true, otherwise not. | |
83 */ | |
84 public HorizontalProfileChart( | |
85 ChartLabels labels, | |
86 ChartTheme theme, | |
87 Collection parameters, | |
88 Collection measurements, | |
89 Collection dates, | |
90 Collection result, | |
91 Collection timeGaps, | |
92 Locale locale, | |
93 boolean linesVisible, | |
94 boolean shapesVisible | |
95 ) { | |
96 super( | |
97 labels, | |
98 theme, | |
99 parameters, | |
100 measurements, | |
101 dates, | |
102 result, | |
103 timeGaps, | |
104 locale, | |
105 linesVisible, | |
106 shapesVisible | |
107 ); | |
108 this.PLOT_ORIENTATION = PlotOrientation.VERTICAL; | |
109 } | |
110 | |
111 | |
112 @Override | |
113 protected Object getValue(Result row) { | |
114 try { | |
115 return (Point) wktReader.read(row.getString("SHAPE")); | |
116 } | |
117 catch(ParseException pe) { | |
118 log.warn("No data found while parsing."); | |
119 return null; | |
120 } | |
121 } | |
122 | |
123 | |
124 @Override | |
125 protected void gapDetection( | |
126 Result[] results, | |
127 Series series, | |
128 int startPos, | |
129 int endPos | |
130 ) { | |
131 log.debug("Start gap detection."); | |
132 try { | |
133 Point startValue = getPoint(results[startPos]); | |
134 Point endValue = getPoint(results[endPos-1]); | |
135 if (results[0].getInteger("DATAID") == 2) | |
136 addGapsOnGrid(results, series, startPos, endPos); | |
137 else | |
138 addGaps( | |
139 results, | |
140 series, | |
141 startValue, | |
142 endValue, | |
143 startPos, | |
144 endPos | |
145 ); | |
146 } | |
147 catch (ParseException pe) { | |
148 log.warn( | |
149 "Error while parsing points for gap detection. " + | |
150 "No gaps for current series will be detected." | |
151 ); | |
152 } | |
153 | |
154 log.debug("Gap detection finished."); | |
155 } | |
156 | |
157 | |
158 @Override | |
159 protected void addValue(Result row, Series series) { | |
160 try { | |
161 Point point = (Point) wktReader.read(row.getString("SHAPE")); | |
162 if (lastPoint != null) { | |
163 distance += DistanceCalculator.calculateDistance( | |
164 lastPoint, point | |
165 ); | |
166 } | |
167 | |
168 ((XYSeries) series).add( | |
169 distance, | |
170 row.getDouble("YORDINATE") | |
171 ); | |
172 | |
173 lastPoint = point; | |
174 } | |
175 catch(ParseException pe) { | |
176 log.warn("No data found while parsing."); | |
177 } | |
178 } | |
179 | |
180 | |
181 @Override | |
182 protected void addSeries(Series series, String label, int idx) { | |
183 super.addSeries(series, label, idx); | |
184 | |
185 // reset lastPoint and distance of the last series | |
186 lastPoint = null; | |
187 distance = 0; | |
188 } | |
189 | |
190 | |
191 @Override | |
192 protected void prepareAxis(String seriesKey,int idx) { | |
193 log.debug("prepare axis of xychart"); | |
194 | |
195 XYPlot plot = chart.getXYPlot(); | |
196 Axis xAxis = plot.getDomainAxis(); | |
197 NumberAxis yAxis = new NumberAxis(seriesKey); | |
198 | |
199 localizeDomainAxis(xAxis, locale); | |
200 localizeRangeAxis(yAxis, locale); | |
201 | |
202 // litte workarround to adjust the max range of axes. | |
203 // NumberAxis.setAutoRange(true) doesn't seem to work properly. | |
204 Range yRange = (Range) ranges.get(seriesKey); | |
205 double lo = yRange.getLowerBound(); | |
206 double hi = yRange.getUpperBound(); | |
207 | |
208 // XXX Special case: only a single value in this chart. | |
209 // JFreeChart doesn't expand this range, because its length is 0. | |
210 if (lo == hi) { | |
211 yRange = new Range( | |
212 lo - (lo / 100 * LOWER_MARGIN), | |
213 hi + (hi / 100 * UPPER_MARGIN)); | |
214 } | |
215 else { | |
216 yRange = Range.expand(yRange, LOWER_MARGIN, UPPER_MARGIN); | |
217 } | |
218 | |
219 yAxis.setRange(yRange); | |
220 log.debug("Max Range of dataset is: " + yRange.toString()); | |
221 | |
222 if (seriesKey.contains("richtung")) { | |
223 yAxis.setTickUnit(new NumberTickUnit(30.0)); | |
224 yAxis.setUpperBound(360.0); | |
225 yAxis.setLowerBound(0.0); | |
226 } | |
227 else { | |
228 yAxis.setFixedDimension(10.0); | |
229 yAxis.setAutoRangeIncludesZero(false); | |
230 } | |
231 | |
232 plot.setRangeAxis(idx, yAxis); | |
233 yAxis.configure(); | |
234 | |
235 if (idx % 2 != 0) | |
236 plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_RIGHT); | |
237 else | |
238 plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_LEFT); | |
239 | |
240 plot.mapDatasetToRangeAxis(idx, idx); | |
241 } | |
242 | |
243 | |
244 @Override | |
245 protected void prepareRangeAxis(String seriesKey, int idx) { | |
246 return; | |
247 // do nothing here | |
248 } | |
249 | |
250 | |
251 @Override | |
252 protected void storeMaxValue(Map values, Object value, String parameter) { | |
253 return; | |
254 // do nothing here | |
255 } | |
256 | |
257 | |
258 @Override | |
259 protected String createSeriesName( | |
260 String breakPoint1, | |
261 String breakPoint2, | |
262 String breakPoint3 | |
263 ) { | |
264 log.debug("create seriesname of horizontalprofile chart"); | |
265 return super.createSeriesName( | |
266 breakPoint1, | |
267 breakPoint2, | |
268 breakPoint3) + | |
269 " " + | |
270 findValueTitle(dates, breakPoint3); | |
271 } | |
272 | |
273 | |
274 @Override | |
275 protected void addGapsOnGrid( | |
276 Result[] results, | |
277 Series series, | |
278 int startPos, | |
279 int endPos | |
280 ) { | |
281 String axis = getDependendAxisName( | |
282 results[startPos], | |
283 results[startPos+1] | |
284 ); | |
285 | |
286 int last = 0; | |
287 int current = 0; | |
288 Point lastPoint = null; | |
289 Point currentPoint = null; | |
290 | |
291 double distance = 0; | |
292 double distanceOld = 0; | |
293 for (int i = startPos+1; i < endPos; i++) { | |
294 try { | |
295 last = results[i-1].getInteger(axis); | |
296 lastPoint = getPoint(results[i-1]); | |
297 current = results[i].getInteger(axis); | |
298 currentPoint = getPoint(results[i]); | |
299 | |
300 distanceOld = distance; | |
301 distance += DistanceCalculator.calculateDistance( | |
302 lastPoint, | |
303 currentPoint); | |
304 | |
305 boolean detected = gridDetection(last, current); | |
306 | |
307 if (log.isDebugEnabled()) { | |
308 log.debug("Last point: " + lastPoint.toString()); | |
309 log.debug("Current point: " + currentPoint.toString()); | |
310 log.debug("Current distance from start: " + distance); | |
311 } | |
312 | |
313 if (detected) { | |
314 log.info( | |
315 "Gap detected on grid between " + distanceOld + | |
316 " and " + distance); | |
317 | |
318 ((XYSeries) series).add(distance-1d, null); | |
319 ((XYSeries) series).add(distanceOld+1d, null); | |
320 } | |
321 } | |
322 catch (ParseException pe) { | |
323 log.warn("Error while parsing point for gap detection.", pe); | |
324 } | |
325 } | |
326 } | |
327 | |
328 | |
329 /** | |
330 * Method to add gaps between two data points. The real detection is done by | |
331 * {@link #simpleDetection} and {@link #specialDetection}. | |
332 * | |
333 * @param results All data points in this dataset. | |
334 * @param series Series to be processed. | |
335 * @param startValue <code>Point</code> where the scan for gaps should begin. | |
336 * @param endValue <code>Point</code> where the scan should end. | |
337 * @param startPos Start position of this series in <code>results</code>. | |
338 * @param endPos End position of a series in <code>results</code> | |
339 */ | |
340 protected void addGaps( | |
341 Result[] results, | |
342 Series series, | |
343 Point startValue, | |
344 Point endValue, | |
345 int startPos, | |
346 int endPos | |
347 ) { | |
348 double range = 0; | |
349 Point last = null; | |
350 Point now = null; | |
351 | |
352 for (int i = startPos+1; i < endPos; i++) { | |
353 boolean detected = false; | |
354 | |
355 try { | |
356 last = (Point) getPoint(results[i-1]); | |
357 now = (Point) getPoint(results[i]); | |
358 | |
359 // gap detection for more than GAP_MAX_VALUES values | |
360 if (results.length > GAP_MAX_VALUES) | |
361 detected = simpleDetection(startValue, endValue, last, now); | |
362 // gap detection for less than GAP_MAX_VALUES values | |
363 else | |
364 detected = specialDetection( | |
365 startValue, | |
366 endValue, | |
367 last, | |
368 now, | |
369 results.length | |
370 ); | |
371 | |
372 // gap detected, insert null value to break line | |
373 if (detected) { | |
374 log.info("Gap after " + range); | |
375 double x = range + 0.0001; | |
376 | |
377 ((XYSeries)series).add(x, null); | |
378 } | |
379 | |
380 range += DistanceCalculator.calculateDistance(last,now); | |
381 } | |
382 catch (ParseException pe) { | |
383 log.warn("Error while parsing point."); | |
384 } | |
385 | |
386 } | |
387 } | |
388 | |
389 | |
390 /** | |
391 * Simple method to detect gaps. A gap is detected if the delta between two | |
392 * data points (current, last) is bigger than <code>PERCENTAGE</code> percent | |
393 * of delta of start and end. | |
394 * <br> | |
395 * (smallDelta > delta / 100 * PERCENTAGE) | |
396 * | |
397 * @param start First data point in a series | |
398 * @param end Last data point in a series | |
399 * @param last Left point | |
400 * @param current Right point | |
401 * | |
402 * @return true, if a gap is detected between last and current - otherwise | |
403 * false. | |
404 */ | |
405 protected boolean simpleDetection( | |
406 Point start, | |
407 Point end, | |
408 Point last, | |
409 Point current | |
410 ) { | |
411 double delta = DistanceCalculator.calculateDistance(start, end); | |
412 double deltaSmall = DistanceCalculator.calculateDistance(last,current); | |
413 | |
414 return (deltaSmall > (delta / 100 * PERCENTAGE)); | |
415 } | |
416 | |
417 | |
418 /** | |
419 * Method to detect gaps between two data points. Following formula is used | |
420 * for detection:<br> | |
421 * smallDelta > (3.0 / (count - 1) * delta)<br> | |
422 * smallDelta = distance between <code>current</code> and <code>last</code> | |
423 * <br> | |
424 * delta = distance between <code>start</code> and <code>end</code> | |
425 * | |
426 * @param start First data point in a series | |
427 * @param end Last data point in a series | |
428 * @param last Left point | |
429 * @param current Right point | |
430 * @param count Number of datapoints | |
431 * @return true, if a gap is detected between last and current - otherwise | |
432 * false. | |
433 */ | |
434 protected boolean specialDetection( | |
435 Point start, | |
436 Point end, | |
437 Point last, | |
438 Point current, | |
439 int count | |
440 ) { | |
441 double delta = Math.abs( | |
442 DistanceCalculator.calculateDistance(end, start) | |
443 ); | |
444 double smallDelta = Math.abs( | |
445 DistanceCalculator.calculateDistance(current, last) | |
446 ); | |
447 | |
448 return (smallDelta > (3.0 / (count - 1) * delta)); | |
449 } | |
450 | |
451 | |
452 @Override | |
453 protected String getDependendAxisName(Result first, Result second) { | |
454 if (first.getInteger("IPOSITION") == second.getInteger("IPOSITION")) | |
455 return "JPOSITION"; | |
456 | |
457 return "IPOSITION"; | |
458 } | |
459 | |
460 /** | |
461 * This method returns a point from a given wkt string stored in | |
462 * <code>result</code>. | |
463 * | |
464 * @param result <code>Result</code> object which contains the wkt string. | |
465 * The wkt string needs to be available under the key SHAPE. | |
466 * | |
467 * @return Point representation of wkt string. | |
468 */ | |
469 private Point getPoint(Result result) | |
470 throws ParseException | |
471 { | |
472 return (Point) wktReader.read(result.getString("SHAPE")); | |
473 } | |
474 } | |
475 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : |