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 &gt; 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 &gt; (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 :

http://dive4elements.wald.intevation.org