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