view artifacts/src/main/java/org/dive4elements/river/exports/DischargeCurveGenerator.java @ 7691:fa4fbd66e752

(issue1579) Fix axes syncronisation at Gauges The SyncNumberAxis was completely broken. It only synced in one direction and even that did not work correctly when data was added to the axis (and the syncAxis rescaled but forgot the old axis) then there were lots of ways to bypass that scaling. And i also think the trans calculation was wrong. It has been replaced by a "mostly" simple method to just keep the W in M and W in CM+Datum axes in sync. I say "Mostly" because it had to deal with the Bounds interface.
author Andre Heinecke <aheinecke@intevation.de>
date Fri, 13 Dec 2013 19:03:00 +0100
parents 2fed93751ecb
children e47b1ea5baf4
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.exports;

import java.util.ArrayList;
import java.util.List;

import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
import org.dive4elements.river.artifacts.D4EArtifact;
import org.dive4elements.river.artifacts.model.FacetTypes;
import org.dive4elements.river.artifacts.model.WQKms;
import org.dive4elements.river.exports.process.DischargeProcessor;
import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
import org.dive4elements.river.jfree.Bounds;
import org.dive4elements.river.jfree.DoubleBounds;
import org.dive4elements.river.jfree.RiverAnnotation;
import org.dive4elements.river.jfree.StickyAxisAnnotation;
import org.dive4elements.river.jfree.StyledXYSeries;
import org.dive4elements.river.model.Gauge;
import org.dive4elements.river.model.River;
import org.dive4elements.river.themes.ThemeDocument;
import org.dive4elements.river.utils.RiverUtils;

import org.apache.log4j.Logger;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.Range;
import org.jfree.data.xy.XYSeries;


/**
 * An OutGenerator that generates discharge curves.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class DischargeCurveGenerator
extends      XYChartGenerator
implements   FacetTypes {

    /** Beware, in this implementation, the W axis is also in cm! */
    public static enum YAXIS {
        WCm(0),
        W(1);
        protected int idx;
        private YAXIS(int c) {
            idx = c;
        }
    }

    /** The logger used in this generator. */
    private static Logger logger =
        Logger.getLogger(DischargeCurveGenerator.class);

    public static final String I18N_CHART_TITLE =
        "chart.discharge.curve.title";

    public static final String I18N_CHART_SUBTITLE =
        "chart.discharge.curve.subtitle";

    public static final String I18N_XAXIS_LABEL =
        "chart.discharge.curve.xaxis.label";

    public static final String I18N_YAXIS_LABEL =
        "chart.discharge.curve.yaxis.label";

    public static final String I18N_CHART_TITLE_DEFAULT  = "Abflusskurven";
    public static final String I18N_XAXIS_LABEL_DEFAULT  = "Q [m\u00b3/s]";
    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [cm]";


    /**
     * Returns the PNP (Datum) of gauge, if at gauge, 0 otherwise.
     */
    public static double getCurrentGaugeDatum(double km, D4EArtifact artifact, double tolerance) {
        // Look if there is a gauge at chosen km:
        // Get gauge which is defined for km
        Gauge gauge =
            RiverUtils.getRiver(artifact).determineGauge(km-0.1d, km+0.1d);
        if (gauge == null) {
            logger.error("No Gauge could be found at station " + km + "!");
            return 0d;
        }
        double subtractPNP = 0d;
        // Compare to km.
        if (Math.abs(km - gauge.getStation().doubleValue()) < tolerance) {
            subtractPNP = gauge.getDatum().doubleValue();
        }
        return subtractPNP;
    }


    /** Get the current Gauge datum with default distance tolerance. */
    public double getCurrentGaugeDatum() {
        return getCurrentGaugeDatum(getRange()[0],
            (D4EArtifact) getMaster(), 1e-4);
    }


    /** Overriden to show second axis also if no visible data present. */
    @Override
    protected void adjustAxes(XYPlot plot) {
        super.adjustAxes(plot);
        if (getCurrentGaugeDatum() != 0d) {
            // Show the W[*m] axis even if there is no data.
            plot.setRangeAxis(1, createYAxis(YAXIS.W.idx));
            syncWAxisRanges();
        }
    }

    protected void syncWAxisRanges() {
        // Syncronizes the ranges of both W Axes to make sure
        // that the Data matches for both axes.
        Bounds boundsInMGauge = getYBounds(YAXIS.W.idx);
        Bounds boundsInCM = getYBounds(YAXIS.WCm.idx);

        // XXX Q-Symetry: I am assuming here that there can only
        // be a fixed Range for WinM as this is currently the only
        // thing that is configureable.
        Range fixedWinMRange = getRangeForAxisFromSettings(
                getYAxisWalker().getId(YAXIS.W.idx));

        // The combination of Range and Bounds is crazy..
        if (fixedWinMRange != null) {
            boundsInMGauge = new DoubleBounds(fixedWinMRange.getLowerBound(),
                    fixedWinMRange.getUpperBound());
        }

        logger.debug("Syncing Axis Bounds. Bounds W: " + boundsInMGauge.toString() +
                " Bounds Wcm: " + boundsInCM.toString());

        double datum = getCurrentGaugeDatum();

        // Convert boundsInMGauge to Datum+cm
        double convertedLower = ((Double)boundsInMGauge.getLower() - datum) * 100;
        double convertedUpper = ((Double)boundsInMGauge.getUpper() - datum) * 100;
        Bounds convertedBounds = new DoubleBounds(convertedLower, convertedUpper);

        // Now combine both Ranges
        boundsInCM = boundsInCM.combine(convertedBounds);

        // Recalculate absolute bounds
        boundsInMGauge = new DoubleBounds((Double)boundsInCM.getLower() / 100d + datum,
                                          (Double)boundsInCM.getUpper() / 100d + datum);

        // Set the new combined bounds
        setYBounds(YAXIS.W.idx, boundsInMGauge);
        setYBounds(YAXIS.WCm.idx, boundsInCM);
        logger.debug("Synced Bounds W: " + boundsInMGauge.toString() +
                " Bounds Wcm: " + boundsInCM.toString());
    }

    public DischargeCurveGenerator() {
        super();
    }


    @Override
    protected YAxisWalker getYAxisWalker() {
        return new YAxisWalker() {
            @Override
            public int length() {
                return YAXIS.values().length;
            }

            @Override
            public String getId(int idx) {
                YAXIS[] yaxes = YAXIS.values();
                return yaxes[idx].toString();
            }
        };
    }


    /**
     * Returns always null to suppress subtitles.
     */
    @Override
    protected String getDefaultChartTitle() {
        return null;
    }


    @Override
    protected String getDefaultXAxisLabel() {
        return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
    }

    @Override
    protected String getDefaultYAxisLabel(int pos) {
        return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
    }


    /* TODO is this one really needed? */
    @Override
    protected boolean zoomX(XYPlot plot, ValueAxis axis, Bounds bounds, Range x) {
        boolean zoomin = super.zoom(plot, axis, bounds, x);

        if (!zoomin) {
            axis.setLowerBound(0d);
        }

        return zoomin;
    }

    /** Translate River annotations if a gauge. */
    public void translateRiverAnnotation(RiverAnnotation riverAnnotation) {
        if (getCurrentGaugeDatum() == 0d) {
            return;
        }
        logger.debug("Translate some river annotation.");
        double translate = getCurrentGaugeDatum();
        double factor    = 100d;
        for (StickyAxisAnnotation annotation: riverAnnotation.getAxisTextAnnotations()){
            if (!annotation.atX()) {
                annotation.setPos((annotation.getPos() - translate)*factor);
            }
        }
        for (XYTextAnnotation annotation: riverAnnotation.getTextAnnotations()) {
            annotation.setY((annotation.getY() - translate)*factor);
        }
    }


    @Override
    public void doOut(
        ArtifactAndFacet artifactFacet,
        ThemeDocument    theme,
        boolean          visible
    ) {
        String name = artifactFacet.getFacetName();
        logger.debug("DischargeCurveGenerator.doOut: " + name);

        DischargeProcessor dProcessor = new DischargeProcessor(getRange()[0]);
        if (dProcessor.canHandle(name)) {
            // In Base DischargeCurveGenerator, always at gauge, use WCm axis.
            dProcessor.doOut(this, artifactFacet, theme, visible, YAXIS.WCm.idx);
        }
        else if (name.equals(DISCHARGE_CURVE)
                || name.equals(GAUGE_DISCHARGE_CURVE)) {
            doDischargeOut(
                (D4EArtifact)artifactFacet.getArtifact(),
                artifactFacet.getData(context),
                artifactFacet.getFacetDescription(),
                theme,
                visible);
        }
        else if (FacetTypes.IS.MANUALPOINTS(name)) {
            doPoints(artifactFacet.getData(context),
                artifactFacet,
                theme, visible, YAXIS.W.idx);
        }
        else if (STATIC_WQ.equals(name)) {
            doWQOut(artifactFacet.getData(context),
                artifactFacet,
                theme,
                visible);
        }
        else {
           logger.warn("DischargeCurveGenerator.doOut: Unknown facet name: " + name);
           return;
        }
    }


    /**
     * Add series with discharge curve to diagram.
     */
    protected void doDischargeOut(
        D4EArtifact artifact,
        Object        o,
        String        description,
        ThemeDocument theme,
        boolean       visible)
    {
        logger.debug("DischargeCurveGenerator.doDischargeOut");
        WQKms wqkms = (WQKms) o;

        String gaugeName = wqkms.getName();

        River river = RiverUtils.getRiver(artifact);

        if (river == null) {
            logger.debug("no river found");
            return;
        }

        Gauge gauge = river.determineGaugeByName(gaugeName);

        if (gauge == null) {
            logger.debug("no gauge found");
            return;
        }

        XYSeries series = new StyledXYSeries(description, theme);

        StyledSeriesBuilder.addPointsQW(series, wqkms);

        addAxisSeries(series, YAXIS.W.idx, visible);
    }

    /**
     * Add W/Q-Series to plot.
     * @param wqkms actual data
     * @param theme theme to use.
     */
    protected void doQOut(
        Object           wqkms,
        ArtifactAndFacet aaf,
        ThemeDocument    theme,
        boolean          visible
    ) {
        logger.debug("DischargeCurveGenerator: doQOut (add W/Q data).");
        XYSeries series = new StyledXYSeries(aaf.getFacetDescription(), theme);

        StyledSeriesBuilder.addPointsQW(series, (WQKms) wqkms);

        addAxisSeries(series, YAXIS.W.idx, visible);
    }


    /** Add a point annotation at given x and y coordinates. */
    protected void addPointTextAnnotation(
        String title,
        double x,
        double y,
        ThemeDocument theme
    ) {
        List<XYTextAnnotation> textAnnos =
            new ArrayList<XYTextAnnotation>();
        XYTextAnnotation anno = new CollisionFreeXYTextAnnotation(
                title,
                x,
                y);
        textAnnos.add(anno);
        RiverAnnotation flysAnno = new RiverAnnotation(
            null, null, null, theme);
        flysAnno.setTextAnnotations(textAnnos);
        addAnnotations(flysAnno);
    }


    /**
     * Return true if all values in data[0] are smaller than zero
     * (in imported data they are set to -1 symbolically).
     * Return false if data is null or empty
     */
    private boolean hasNoDischarge(double[][] data) {
        if (data == null || data.length == 0) {
            return false;
        }

        double[] qs = data[0];
        for (double q: qs) {
            if (q > 0d) {
                return false;
            }
        }

        return true;
    }


    /**
     * Add WQ Data to plot.
     * @param wq data as double[][]
     */
    protected void doWQOut(
        Object           wq,
        ArtifactAndFacet aaf,
        ThemeDocument    theme,
        boolean          visible
    ) {
        logger.debug("DischargeCurveGenerator: doWQOut");
        double [][] data = (double [][]) wq;
        String title = aaf.getFacetDescription();

        double translate = getCurrentGaugeDatum();

        // If no Q values (i.e. all -1) found, add annotations.
        if (hasNoDischarge(data)) {
            List<StickyAxisAnnotation> xy = new ArrayList<StickyAxisAnnotation>();

            for (double y: data[1]) {
                if (translate != 0d) {
                    y = (y-translate)*100d;
                }

                xy.add(new StickyAxisAnnotation(
                    title,
                    (float) y,
                    StickyAxisAnnotation.SimpleAxis.Y_AXIS));
            }

            doAnnotations(
                new RiverAnnotation(title, xy),
                aaf, theme, visible);
            return;
        }

        // Otherwise add points.
        XYSeries series = new StyledXYSeries(title, theme);

        if (translate != 0d) {
            StyledSeriesBuilder.addPointsQW(series, data, -translate, 100d);
            addAxisSeries(series, YAXIS.W.idx, visible);
        }
        else {
            StyledSeriesBuilder.addPoints(series, data, true);
            addAxisSeries(series, YAXIS.W.idx, visible);
        }

        if (visible && theme.parseShowPointLabel()
            && data != null && data.length != 0) {

            double[] xs = data[0];
            double[] ys = data[1];
            for (int i = 0; i < xs.length; i++) {
                double x = xs[i];
                double y = ys[i];

                if (translate != 0d) {
                    y = (y-translate)*100d;
                }

                addPointTextAnnotation(title, x, y, theme);
            }
        }
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org