view artifacts/src/main/java/org/dive4elements/river/exports/DischargeCurveGenerator.java @ 9556:9b8e8fc1f408

Use facetName in all processors as themeType for legend aggregation.
author gernotbelger
date Tue, 23 Oct 2018 16:26:58 +0200
parents 740d65e4aa14
children
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.apache.log4j.Logger;
import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
import org.dive4elements.artifactdatabase.state.State;
import org.dive4elements.artifacts.CallContext;
import org.dive4elements.river.artifacts.D4EArtifact;
import org.dive4elements.river.artifacts.GaugeDischargeCurveArtifact;
import org.dive4elements.river.artifacts.access.RiverAccess;
import org.dive4elements.river.artifacts.model.FacetTypes;
import org.dive4elements.river.artifacts.model.WQKms;
import org.dive4elements.river.exports.process.MiscDischargeProcessor;
import org.dive4elements.river.jfree.Bounds;
import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
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.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(final int c) {
            this.idx = c;
        }
    }

    /** The log used in this generator. */
    private static Logger log = 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 = "common.export.csv.header.q";

    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(final double km, final D4EArtifact artifact, final double tolerance) {
        // Look if there is a gauge at chosen km:
        // Get gauge which is defined for km
        final Gauge gauge = new RiverAccess(artifact).getRiver().determineGaugeAtStation(km);
        if (gauge == null) {
            log.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(final XYPlot plot) {
        super.adjustAxes(plot);
        // XXX Hacking around that there were two axes shown in official Gauge
        // Discharge, the one from the WINFO module.
        // This should be made unecessary in a Q Diagram refactoring with
        // decent inheritance.
        if (getMaster() instanceof GaugeDischargeCurveArtifact) {
            final GaugeDischargeCurveArtifact myMaster = (GaugeDischargeCurveArtifact) getMaster();
            final State state = myMaster.getCurrentState(getContext());
            if (GaugeDischargeCurveArtifact.STATIC_STATE_NAME.equals(state.getID())) {
                return;
            }
        }
        // End Hack

        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);

        if (boundsInMGauge == null || boundsInCM == null) {
            // One axis does not exist. Nothing to sync
            return;
        }

        // 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.
        final 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());
        }

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

        final double datum = getCurrentGaugeDatum();

        // Convert boundsInMGauge to Datum+cm
        final double convertedLower = ((Double) boundsInMGauge.getLower() - datum) * 100;
        final double convertedUpper = ((Double) boundsInMGauge.getUpper() - datum) * 100;
        final 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);
        log.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(final int idx) {
                final YAXIS[] yaxes = YAXIS.values();
                return yaxes[idx].toString();
            }
        };
    }

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

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

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

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

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

        return zoomin;
    }

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

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

        final CallContext context = getContext();

        final MiscDischargeProcessor dProcessor = new MiscDischargeProcessor(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(artifactFacet.getFacetName(),(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 {
            log.warn("DischargeCurveGenerator.doOut: Unknown facet name: " + name);
            return;
        }
    }

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

        final String gaugeName = wqkms.getName();

        final River river = new RiverAccess(artifact).getRiver();

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

        final Gauge gauge = river.determineGaugeByName(gaugeName);

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

        final XYSeries series = new StyledXYSeries(facetName, 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(final Object wqkms, final ArtifactAndFacet aaf, final ThemeDocument theme, final boolean visible) {
        log.debug("DischargeCurveGenerator: doQOut (add W/Q data).");
        final XYSeries series = new StyledXYSeries(aaf.getFacetName(), 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(final String title, final double x, final double y, final ThemeDocument theme) {
        final List<XYTextAnnotation> textAnnos = new ArrayList<>();
        final XYTextAnnotation anno = new CollisionFreeXYTextAnnotation(title, x, y);
        textAnnos.add(anno);
        final 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 static boolean hasNoDischarge(final double[][] data) {
        if (data == null || data.length == 0) {
            return false;
        }

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

        return true;
    }

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

        final double translate = getCurrentGaugeDatum();

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

            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.
        final XYSeries series = new StyledXYSeries(aaf.getFacetName(),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) {

            final double[] xs = data[0];
            final double[] ys = data[1];
            for (int i = 0; i < xs.length; i++) {
                final 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