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

http://dive4elements.wald.intevation.org