view gnv-artifacts/src/main/java/de/intevation/gnv/chart/HorizontalProfileChart.java @ 1053:6169ddc827ac

The format of a histogram pdf depends on the width and height specified by the user - or the default size (issue290). gnv-artifacts/trunk@1127 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Wed, 26 May 2010 07:00:55 +0000
parents 778d86255d76
children f953c9a559d8
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.
     */
    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