Mercurial > dive4elements > gnv-client
view gnv-artifacts/src/main/java/de/intevation/gnv/chart/HorizontalProfileChart.java @ 1038:9981452c7e75
First step: Added a new state handling the selection between vector or scalar and a new transition in timeseries to provide vector values (issue27).
gnv-artifacts/trunk@1110 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Tue, 18 May 2010 16:28:05 +0000 |
parents | 38c8cc586a85 |
children | 778d86255d76 |
line wrap: on
line source
package de.intevation.gnv.chart; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import de.intevation.gnv.geobackend.base.Result; import de.intevation.gnv.utils.DistanceCalculator; import java.util.Collection; import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import org.jfree.chart.ChartTheme; import org.jfree.chart.axis.Axis; import org.jfree.chart.axis.AxisLocation; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.XYPlot; import org.jfree.data.Range; import org.jfree.data.general.Series; import org.jfree.data.xy.XYSeries; /** * This class is used to create xy-charts of horizontal profiles. * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class HorizontalProfileChart extends VerticalProfileChart { /** * Logger used for logging with log4j. */ private static Logger log = Logger.getLogger(HorizontalProfileChart.class); /** * <code>WKTReader</code> used to turn wkt strings into geometries. */ private static WKTReader wktReader = new WKTReader(); /** * The first point in a HorizontalProfileChart. It is used to calculate the * distance between the currently processed point an the start. */ private Point firstPoint; /** * Constructor used to create horizontal profile charts. * * @param labels Labels used to be displayed in title, subtitle and so on. * @param theme ChartTheme used to adjust the rendering of this chart. * @param parameters Collection containing a bunch of parameters. * @param measurements Collection containing a bunch of measurements. * @param dates Collection containing a bunch of date objects. * @param result Collection containing a bunch of <code>Result</code> * objects which contain the actual data items to be displayed. * @param timeGaps Collection with timegap definitions. * @param locale Locale used to specify the format of labels, numbers, ... * @param linesVisible Render lines between data points if true, otherwise * not. * @param shapesVisible Render vertices as points if true, otherwise not. */ public HorizontalProfileChart( ChartLabels labels, ChartTheme theme, Collection parameters, Collection measurements, Collection dates, Collection result, Collection timeGaps, Locale locale, boolean linesVisible, boolean shapesVisible ) { super( labels, theme, parameters, measurements, dates, result, timeGaps, locale, linesVisible, shapesVisible ); this.PLOT_ORIENTATION = PlotOrientation.VERTICAL; } @Override protected Object getValue(Result row) { try { return (Point) wktReader.read(row.getString("SHAPE")); } catch(ParseException pe) { log.warn("No data found while parsing."); return null; } } @Override protected void gapDetection( Result[] results, Series series, int startPos, int endPos ) { log.debug("Start gap detection."); try { Point startValue = getPoint(results[startPos]); Point endValue = getPoint(results[endPos-1]); if (results[0].getInteger("DATAID") == 2) addGapsOnGrid(results, series, startPos, endPos); else addGaps( results, series, startValue, endValue, startPos, endPos ); } catch (ParseException pe) { log.warn( "Error while parsing points for gap detection. " + "No gaps for current series will be detected." ); } log.debug("Gap detection finished."); } @Override protected void addValue(Result row, Series series) { double distance = 0; try { Point point = (Point) wktReader.read(row.getString("SHAPE")); if (firstPoint != null) { distance = DistanceCalculator.calculateDistance( firstPoint, point ); } else { firstPoint = point; } ((XYSeries) series).add( distance, row.getDouble("YORDINATE") ); } catch(ParseException pe) { log.warn("No data found while parsing."); } } @Override protected void addSeries(Series series, String label, int idx) { super.addSeries(series, label, idx); // reset firstPoint for next series firstPoint = null; } @Override protected void prepareAxis(String seriesKey,int idx) { log.debug("prepare axis of xychart"); XYPlot plot = chart.getXYPlot(); Axis xAxis = plot.getDomainAxis(); NumberAxis yAxis = new NumberAxis(seriesKey); localizeDomainAxis(xAxis, locale); localizeRangeAxis(yAxis, locale); // litte workarround to adjust the max range of axes. // NumberAxis.setAutoRange(true) doesn't seem to work properly. Range yRange = (Range) ranges.get(seriesKey); double lo = yRange.getLowerBound(); double hi = yRange.getUpperBound(); // XXX Special case: only a single value in this chart. // JFreeChart doesn't expand this range, because its length is 0. if (lo == hi) { yRange = new Range( lo - (lo / 100 * LOWER_MARGIN), hi + (hi / 100 * UPPER_MARGIN)); } else { yRange = Range.expand(yRange, LOWER_MARGIN, UPPER_MARGIN); } yAxis.setRange(yRange); log.debug("Max Range of dataset is: " + yRange.toString()); if (seriesKey.contains("richtung")) { yAxis.setTickUnit(new NumberTickUnit(30.0)); yAxis.setUpperBound(360.0); yAxis.setLowerBound(0.0); } else { yAxis.setFixedDimension(10.0); yAxis.setAutoRangeIncludesZero(false); } plot.setRangeAxis(idx, yAxis); yAxis.configure(); if (idx % 2 != 0) plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_RIGHT); else plot.setRangeAxisLocation(idx, AxisLocation.BOTTOM_OR_LEFT); plot.mapDatasetToRangeAxis(idx, idx); } @Override protected void prepareRangeAxis(String seriesKey, int idx) { return; // do nothing here } @Override protected void storeMaxValue(Map values, Object value, String parameter) { return; // do nothing here } @Override protected String createSeriesName( String breakPoint1, String breakPoint2, String breakPoint3 ) { log.debug("create seriesname of horizontalprofile chart"); return super.createSeriesName( breakPoint1, breakPoint2, breakPoint3) + " " + findValueTitle(dates, breakPoint3); } @Override protected void addGapsOnGrid( Result[] results, Series series, int startPos, int endPos ) { String axis = getDependendAxisName( results[startPos], results[startPos+1] ); int last = 0; int current = 0; Point lastPoint = null; Point currentPoint = null; try { firstPoint = getPoint(results[0]); } catch (ParseException pe) { log.error("Unable to parse start point for gap detection."); return; } for (int i = startPos+1; i < endPos; i++) { try { last = results[i-1].getInteger(axis); lastPoint = getPoint(results[i-1]); current = results[i].getInteger(axis); currentPoint = getPoint(results[i]); double distance = DistanceCalculator.calculateDistance( firstPoint, currentPoint); double distanceOld = DistanceCalculator.calculateDistance( firstPoint, lastPoint); boolean detected = gridDetection(last, current); if (log.isDebugEnabled()) { log.debug("Last point: " + lastPoint.toString()); log.debug("Current point: " + currentPoint.toString()); log.debug("Current distance from start: " + distance); } if (detected) { log.info( "Gap detected on grid between " + distanceOld + " and " + distance); ((XYSeries) series).add(distance-1d, null); ((XYSeries) series).add(distanceOld+1d, null); } } catch (ParseException pe) { log.warn("Error while parsing point for gap detection.", pe); } } } /** * Method to add gaps between two data points. The real detection is done by * {@link #simpleDetection} and {@link #specialDetection}. * * @param results All data points in this dataset. * @param series Series to be processed. * @param startValue <code>Point</code> where the scan for gaps should begin. * @param endValue <code>Point</code> where the scan should end. * @param startPos Start position of this series in <code>results</code>. * @param endPos End position of a series in <code>results</code> */ protected void addGaps( Result[] results, Series series, Point startValue, Point endValue, int startPos, int endPos ) { double range = 0; Point last = null; Point now = null; for (int i = startPos+1; i < endPos; i++) { boolean detected = false; try { last = (Point) getPoint(results[i-1]); now = (Point) getPoint(results[i]); // gap detection for more than GAP_MAX_VALUES values if (results.length > GAP_MAX_VALUES) detected = simpleDetection(startValue, endValue, last, now); // gap detection for less than GAP_MAX_VALUES values else detected = specialDetection( startValue, endValue, last, now, results.length ); // gap detected, insert null value to break line if (detected) { log.info("Gap after " + range); double x = range + 0.0001; ((XYSeries)series).add(x, null); } range += DistanceCalculator.calculateDistance(last,now); } catch (ParseException pe) { log.warn("Error while parsing point."); } } } /** * Simple method to detect gaps. A gap is detected if the delta between two * data points (current, last) is bigger than <code>PERCENTAGE</code> percent * of delta of start and end. * <br> * (smallDelta > delta / 100 * PERCENTAGE) * * @param start First data point in a series * @param end Last data point in a series * @param last Left point * @param current Right point * * @return true, if a gap is detected between last and current - otherwise * false. */ protected boolean simpleDetection( Point start, Point end, Point last, Point current ) { double delta = DistanceCalculator.calculateDistance(start, end); double deltaSmall = DistanceCalculator.calculateDistance(last,current); return (deltaSmall > (delta / 100 * PERCENTAGE)); } /** * Method to detect gaps between two data points. Following formula is used * for detection:<br> * smallDelta > (3.0 / (count - 1) * delta)<br> * smallDelta = distance between <code>current</code> and <code>last</code> * <br> * delta = distance between <code>start</code> and <code>end</code> * * @param start First data point in a series * @param end Last data point in a series * @param last Left point * @param current Right point * @param count Number of datapoints * @return true, if a gap is detected between last and current - otherwise * false. */ protected boolean specialDetection( Point start, Point end, Point last, Point current, int count ) { double delta = Math.abs( DistanceCalculator.calculateDistance(end, start) ); double smallDelta = Math.abs( DistanceCalculator.calculateDistance(current, last) ); return (smallDelta > (3.0 / (count - 1) * delta)); } @Override protected String getDependendAxisName(Result first, Result second) { if (first.getInteger("IPOSITION") == second.getInteger("IPOSITION")) return "JPOSITION"; return "IPOSITION"; } /** * This method returns a point from a given wkt string stored in * <code>result</code>. * * @param result <code>Result</code> object which contains the wkt string. * The wkt string needs to be available under the key SHAPE. * * @return Point representation of wkt string. */ private Point getPoint(Result result) throws ParseException { return (Point) wktReader.read(result.getString("SHAPE")); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :