diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gnv-artifacts/src/main/java/de/intevation/gnv/chart/HorizontalProfileChart.java	Fri Sep 28 12:14:00 2012 +0200
@@ -0,0 +1,475 @@
+/*
+ * Copyright (c) 2010 by Intevation GmbH
+ *
+ * This program is free software under the LGPL (>=v2.1)
+ * Read the file LGPL.txt coming with the software for details
+ * or visit http://www.gnu.org/licenses/ if it does not exist.
+ */
+
+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.
+     */
+    protected Point lastPoint;
+
+    protected double distance = 0d;
+
+    /**
+     * 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) {
+        try {
+            Point point = (Point) wktReader.read(row.getString("SHAPE"));
+            if (lastPoint != null) {
+                distance += DistanceCalculator.calculateDistance(
+                    lastPoint, point
+                );
+            }
+
+            ((XYSeries) series).add(
+                distance,
+                row.getDouble("YORDINATE")
+            );
+
+            lastPoint = point;
+        }
+        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 lastPoint and distance of the last series
+        lastPoint = null;
+        distance  = 0;
+    }
+
+
+    @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;
+
+        double distance    = 0;
+        double distanceOld = 0;
+        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]);
+
+                distanceOld  = distance;
+                distance    += DistanceCalculator.calculateDistance(
+                    lastPoint,
+                    currentPoint);
+
+                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 &gt; 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 &gt; (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 :

http://dive4elements.wald.intevation.org