view flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java @ 4282:8b4988815974

Added marker for Ws and Qs in Historical Discharge WQ charts. Therefore, the XYChartGenerator got two new methods addDomainMarker(Marker, boolean) and addValueMarker(Marker, boolean). The boolean parameters determine, if the marker should be visible or not. This is analogous to addAxisSeries(XYSeries, int, boolean).
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Mon, 29 Oct 2012 05:59:27 +0100
parents 6772e9f9b65f
children 0eca080fc162
line wrap: on
line source
package de.intevation.flys.exports;

import de.intevation.artifactdatabase.state.ArtifactAndFacet;
import de.intevation.artifactdatabase.state.Facet;
import de.intevation.flys.artifacts.FLYSArtifact;
import de.intevation.flys.artifacts.geom.Lines;
import de.intevation.flys.artifacts.model.AreaFacet;
import de.intevation.flys.artifacts.model.FacetTypes;
import de.intevation.flys.artifacts.model.WKms;
import de.intevation.flys.artifacts.model.WQKms;
import de.intevation.flys.jfree.FLYSAnnotation;
import de.intevation.flys.jfree.StyledAreaSeriesCollection;
import de.intevation.flys.jfree.StyledXYSeries;
import de.intevation.flys.utils.DataUtil;
import de.intevation.flys.utils.FLYSUtils;
import de.intevation.flys.utils.ThemeUtil;

import org.apache.log4j.Logger;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYSeries;
import org.w3c.dom.Document;


/**
 * An OutGenerator that generates longitudinal section curves.
 *
 * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
 */
public class LongitudinalSectionGenerator
extends      XYChartGenerator
implements   FacetTypes
{
    public enum YAXIS {
        W(0),
        D(1),
        Q(2);
        protected int idx;
        private YAXIS(int c) {
            idx = c;
        }
    }

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

    /** Key to look up internationalized String for annotations label. */
    public static final String I18N_ANNOTATIONS_LABEL =
        "chart.longitudinal.annotations.label";

    /**
     * Key to look up internationalized String for LongitudinalSection diagrams
     * titles.
     */
    public static final String I18N_CHART_TITLE =
        "chart.longitudinal.section.title";

    /**
     * Key to look up internationalized String for LongitudinalSection diagrams
     * subtitles.
     */
    public static final String I18N_CHART_SUBTITLE =
        "chart.longitudinal.section.subtitle";

    /**
     * Key to look up internationalized String for LongitudinalSection diagrams
     * short subtitles.
     */
    public static final String I18N_CHART_SHORT_SUBTITLE =
        "chart.longitudinal.section.shortsubtitle";

    public static final String I18N_XAXIS_LABEL =
        "chart.longitudinal.section.xaxis.label";

    public static final String I18N_YAXIS_LABEL =
        "chart.longitudinal.section.yaxis.label";

    public static final String I18N_2YAXIS_LABEL =
        "chart.longitudinal.section.yaxis.second.label";

    public static final String I18N_CHART_TITLE_DEFAULT  = "W-L\u00e4ngsschnitt";
    public static final String I18N_XAXIS_LABEL_DEFAULT  = "km";
    public static final String I18N_YAXIS_LABEL_DEFAULT  = "W [NN + m]";
    public static final String I18N_2YAXIS_LABEL_DEFAULT = "Q [m\u00b3/s]";

    public final static String I18N_WDIFF_YAXIS_LABEL =
        "chart.w_differences.yaxis.label";

    public final static String I18N_WDIFF_YAXIS_LABEL_DEFAULT = "m";

    /** Whether or not the plot is inverted (left-right). */
    protected boolean inverted;


    public LongitudinalSectionGenerator() {
        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();
            }
        };
    }


    /** True if x axis has been inverted. */
    public boolean isInverted() {
        return inverted;
    }


    /** Set to true if x axis has been inverted. */
    public void setInverted(boolean inverted) {
        this.inverted = inverted;
    }

    /**
     * Return left most data points x value (on first axis).
     * Overridden because axis could be inverted.
     */
    @Override
    protected double getLeftX() {
        if (isInverted()) {
            return (Double)getXBounds(0).getUpper();
        }
        return (Double)getXBounds(0).getLower();
    }


    /**
     * Return right most data points x value (on first axis).
     * Overridden because axis could be inverted.
     */
    @Override
    protected double getRightX() {
        if (isInverted()) {
            return (Double)getXBounds(0).getLower();
        }
        return (Double)getXBounds(0).getUpper();
    }


    /**
     * Returns the default title for this chart.
     *
     * @return the default title for this chart.
     */
    @Override
    public String getDefaultChartTitle() {
        return msg(I18N_CHART_TITLE, I18N_CHART_TITLE_DEFAULT);
    }


    /**
     * Returns the default subtitle for this chart.
     *
     * @return the default subtitle for this chart.
     */
    @Override
    protected String getDefaultChartSubtitle() {
        double[] dist = getRange();

        Object[] args = null;
        if (dist == null) {
            args = new Object[] {getRiverName()};
            return msg(getChartShortSubtitleKey(), "", args);
        }
        args = new Object[] {
            getRiverName(),
            dist[0],
            dist[1]
        };
        return msg(getChartSubtitleKey(), "", args);
    }


    /**
     * Gets key to look up internationalized String for the charts subtitle.
     * @return key to look up translated subtitle.
     */
    protected String getChartSubtitleKey() {
        return I18N_CHART_SUBTITLE;
    }


    /**
     * Gets key to look up internationalized String for the charts short
     * subtitle.
     * @return key to look up translated subtitle.
     */
    protected String getChartShortSubtitleKey() {
        return I18N_CHART_SHORT_SUBTITLE;
    }


    /**
     * Get internationalized label for the x axis.
     */
    @Override
    protected String getDefaultXAxisLabel() {
        FLYSArtifact flys = (FLYSArtifact) master;

        return msg(
            I18N_XAXIS_LABEL,
            I18N_XAXIS_LABEL_DEFAULT,
            new Object[] { FLYSUtils.getRiver(flys).getName() });
    }


    @Override
    protected String getDefaultYAxisLabel(int index) {
        String label = "default";

        if (index == YAXIS.W.idx) {
            label = getWAxisLabel();
        }
        else if (index == YAXIS.Q.idx) {
            label = msg(getQAxisLabelKey(), getQAxisDefaultLabel());
        }
        else if (index == YAXIS.D.idx) {
            label = msg(I18N_WDIFF_YAXIS_LABEL, I18N_WDIFF_YAXIS_LABEL_DEFAULT);
        }

        return label;
    }


    /**
     * Get internationalized label for the y axis.
     */
    protected String getWAxisLabel() {
        FLYSArtifact flys = (FLYSArtifact) master;

        String unit = FLYSUtils.getRiver(flys).getWstUnit().getName();

        return msg(
            I18N_YAXIS_LABEL,
            I18N_YAXIS_LABEL_DEFAULT,
            new Object[] { unit });
    }


    /**
     * Create Axis for given index.
     * @return axis with according internationalized label.
     */
    @Override
    protected NumberAxis createYAxis(int index) {
        NumberAxis axis = super.createYAxis(index);

        // "Q" Axis shall include 0.
        if (index == YAXIS.Q.idx) {
            axis.setAutoRangeIncludesZero(true);
        }
        else {
            axis.setAutoRangeIncludesZero(false);
        }

        return axis;
    }


    /**
     * Get default value for the second Y-Axis' label (if no translation was
     * found).
     */
    protected String getQAxisDefaultLabel() {
        return I18N_2YAXIS_LABEL_DEFAULT;
    }


    /**
     * Get key for internationalization of the second Y-Axis' label.
     */
    protected String getQAxisLabelKey() {
        return I18N_2YAXIS_LABEL;
    }


    /**
     * Trigger inversion.
     */
    @Override
    protected void adjustAxes(XYPlot plot) {
        super.adjustAxes(plot);
        invertXAxis(plot.getDomainAxis());
    }


    /**
     * This method inverts the x-axis based on the kilometer information of the
     * selected river. If the head of the river is at kilometer 0, the axis is
     * not inverted, otherwise it is.
     *
     * @param xaxis The domain axis.
     */
    protected void invertXAxis(ValueAxis xaxis) {
        if (inverted) {
            logger.debug("X-Axis.setInverted(true)");
            xaxis.setInverted(true);
        }
    }


    /**
     * Produce output.
     * @param artifactAndFacet current facet and artifact.
     * @param attr  theme for facet
     */
    @Override
    public void doOut(
        ArtifactAndFacet artifactAndFacet,
        Document         attr,
        boolean          visible
    ) {
        String name = artifactAndFacet.getFacetName();

        logger.debug("LongitudinalSectionGenerator.doOut: " + name);

        if (name == null) {
            logger.error("No facet name for doOut(). No output generated!");
            return;
        }

        Facet facet = artifactAndFacet.getFacet();

        if (facet == null) {
            return;
        }

        if (name.equals(LONGITUDINAL_W)) {
            doWOut(
                (WQKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (name.equals(LONGITUDINAL_Q)) {
            doQOut(
                (WQKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (name.equals(LONGITUDINAL_ANNOTATION)) {
            doAnnotations(
                (FLYSAnnotation) artifactAndFacet.getData(context),
                 artifactAndFacet,
                 attr,
                 visible);
        }
        else if (name.equals(STATIC_WKMS)
                || name.equals(HEIGHTMARKS_POINTS)
                || name.equals(STATIC_WQKMS)) {
            doWOut(
                (WKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (name.equals(STATIC_WQKMS_W)) {
            doWOut(
                (WQKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (name.equals(STATIC_WQKMS_Q)) {
            doQOut(
                (WQKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (name.equals(W_DIFFERENCES)) {
            doWDifferencesOut(
                (WKms) artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (FacetTypes.IS.AREA(name)) {
            doArea(
                artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible);
        }
        else if (FacetTypes.IS.MANUALPOINTS(name)) {
            doPoints(
                artifactAndFacet.getData(context),
                artifactAndFacet,
                attr,
                visible,
                YAXIS.W.idx);
        }
        else {
            logger.warn("Unknown facet name: " + name);
            return;
        }
    }


    /**
     * Process the output for W facets in a longitudinal section curve.
     *
     * @param wkms WKms data.
     * @param aandf The artifact and facet. This facet does NOT support any data objects. Use
     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
     * data.
     * @param theme The theme that contains styling information.
     * @param visible The visibility of the curve.
     */
    protected void doWOut(
        WKms             wkms,
        ArtifactAndFacet aandf,
        Document         theme,
        boolean          visible
    ) {
        logger.debug("LongitudinalSectionGenerator.doWOut");

        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);

        StyledSeriesBuilder.addPoints(series, wkms);
        addAxisSeries(series, YAXIS.W.idx, visible);

        // If a "band around the curve shall be drawn, add according area.
        double bandWidth = ThemeUtil.parseBandWidth(theme);
        if (bandWidth > 0 ) {
            XYSeries seriesDown = new StyledXYSeries(
                "band " + aandf.getFacetDescription(), false, theme);
            XYSeries seriesUp = new StyledXYSeries(
                aandf.getFacetDescription()+"+/-"+bandWidth, false, theme);
            StyledSeriesBuilder.addUpperBand(seriesUp, wkms, bandWidth);
            StyledSeriesBuilder.addLowerBand(seriesDown, wkms, bandWidth);

            StyledAreaSeriesCollection area = new StyledAreaSeriesCollection(theme);
            area.addSeries(seriesUp);
            area.addSeries(seriesDown);
            area.setMode(StyledAreaSeriesCollection.FILL_MODE.BETWEEN);
            addAreaSeries(area, YAXIS.W.idx, visible);
        }

        if (needInvertAxis(wkms)) {
            setInverted(true);
        }
    }


    /**
     * Add items to dataseries which describes the differences.
     */
    protected void doWDifferencesOut(
        WKms       wkms,
        ArtifactAndFacet aandf,
        Document   theme,
        boolean    visible
    ) {
        logger.debug("WDifferencesCurveGenerator.doWDifferencesOut");
        if (wkms == null) {
            logger.warn("No data to add to WDifferencesChart.");
            return;
         }

        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);

        if (logger.isDebugEnabled()) {
            if (wkms.size() > 0) {
                logger.debug("Generate series: " + series.getKey());
                logger.debug("Start km: " + wkms.getKm(0));
                logger.debug("End   km: " + wkms.getKm(wkms.size() - 1));
                logger.debug("Values  : " + wkms.size());
            }
        }

        StyledSeriesBuilder.addPoints(series, wkms);

        addAxisSeries(series, YAXIS.D.idx, visible);
        if (DataUtil.guessWaterIncreasing(wkms.allWs())) {
            setInverted(true);
        }
    }


    /**
     * Process the output for Q facets in a longitudinal section curve.
     *
     * @param wqkms An array of WQKms values.
     * @param aandf The facet and artifact. This facet does NOT support any data objects. Use
     * FLYSArtifact.getNativeFacet() instead to retrieve a Facet which supports
     * data.
     * @param theme The theme that contains styling information.
     * @param visible The visibility of the curve.
     */
    protected void doQOut(
        WQKms    wqkms,
        ArtifactAndFacet aandf,
        Document theme,
        boolean  visible
    ) {
        logger.debug("LongitudinalSectionGenerator.doQOut");

        XYSeries series = new StyledXYSeries(aandf.getFacetDescription(), theme);

        StyledSeriesBuilder.addStepPointsKmQ(series, wqkms);

        addAxisSeries(series, YAXIS.Q.idx, visible);

        if (needInvertAxis(wqkms)) {
            setInverted(true);
        }
    }

    /**
     * This method determines - taking JFreeCharts auto x value ordering into
     * account - if the x axis need to be inverted. Waterlines in these charts
     * should decrease.
     *
     * @param wkms The data object that stores the x and y values used for this
     * chart.
     */
    public boolean needInvertAxis(WKms wkms) {
        boolean wsUp = wkms.guessWaterIncreasing();
        boolean kmUp = DataUtil.guessWaterIncreasing(wkms.allKms());
        boolean inv = (wsUp && kmUp) || (!wsUp && !kmUp);

        int size = wkms.size();

        if (logger.isDebugEnabled()) {
            logger.debug("(Wkms)Values  : " + size);
            if (size > 0) {
                logger.debug("Start km: " + wkms.getKm(0));
                logger.debug("End   km: " + wkms.getKm(size-1));
            }
            logger.debug("wsUp: " + wsUp);
            logger.debug("kmUp: " + kmUp);
            logger.debug("inv:  " + inv);
        }

        return inv;
    }


    /**
     * Get name of series (displayed in legend).
     * @return name of the series.
     */
    protected String getSeriesName(WQKms wqkms, String mode) {
        String name   = wqkms.getName();
        String prefix = name != null && name.indexOf(mode) >= 0 ? null : mode;

        return prefix != null && prefix.length() > 0
            ? prefix + "(" + name +")"
            : name;
    }


    /** Look up the axis identifier for a given facet type. */
    public int axisIdxForFacet(String facetName) {
        if (FacetTypes.IS.W(facetName)) {
            return YAXIS.W.idx;
        }
        else if (FacetTypes.IS.Q(facetName)) {
            return YAXIS.Q.idx;
        }
        else {
            logger.warn("Could not find axis for facet " + facetName);
            return YAXIS.W.idx;
        }
    }


    /**
     * Do Area out.
     * @param theme styling information.
     * @param visible whether or not visible.
     */
    protected void doArea(
        Object     o,
        ArtifactAndFacet aandf,
        Document   theme,
        boolean    visible
    ) {
        logger.debug("LongitudinalSectionGenerator.doArea");
        StyledAreaSeriesCollection area = new StyledAreaSeriesCollection(theme);

        String seriesName = aandf.getFacetDescription();

        AreaFacet.Data data = (AreaFacet.Data) o;

        XYSeries up   = null;
        XYSeries down = null;

        if (data.getUpperData() != null) {
            up = new StyledXYSeries(seriesName, false, theme);
            if (data.getUpperData() instanceof WQKms) {
                if (FacetTypes.IS.Q(data.getRootFacetName())) {
                    StyledSeriesBuilder.addPointsKmQ(up, (WQKms) data.getUpperData());
                }
                else {
                    StyledSeriesBuilder.addPoints(up, (WKms) data.getUpperData());
                }
            }
            else if (data.getUpperData() instanceof double[][]) {
                StyledSeriesBuilder.addPoints(up, (double [][]) data.getUpperData(), false);
            }
            else if (data.getUpperData() instanceof WKms) {
                StyledSeriesBuilder.addPoints(up, (WKms) data.getUpperData());
            }
            else if (data.getUpperData() instanceof Lines.LineData) {
                StyledSeriesBuilder.addPoints(up, ((Lines.LineData) data.getUpperData()).points, false);
            }
            else {
                logger.error("Do not know how to deal with (up) area info from: "
                    + data.getUpperData());
            }
        }

        // TODO Depending on style, the area (e.g. 20m^2) should be added as annotation.

        if (data.getLowerData() != null) {
            // TODO: Sort this out: when the two series have the same name,
            // the renderer (or anything in between) will not work correctly.
            down = new StyledXYSeries(seriesName + " ", false, theme);
            if (data.getLowerData() instanceof WQKms) {
                if (FacetTypes.IS.Q(data.getRootFacetName())) {
                    StyledSeriesBuilder.addPointsKmQ(down, (WQKms) data.getLowerData());
                }
                else {
                    StyledSeriesBuilder.addPoints(down, (WQKms) data.getLowerData());
                }
            }
            else if (data.getLowerData() instanceof double[][]) {
                StyledSeriesBuilder.addPoints(down, (double[][]) data.getLowerData(), false);
            }
            else if (data.getLowerData() instanceof WKms) {
                StyledSeriesBuilder.addPoints(down, (WKms) data.getLowerData());
            }
            else if (data.getLowerData() instanceof Lines.LineData) {
                StyledSeriesBuilder.addPoints(down, ((Lines.LineData) data.getLowerData()).points, false);
            }
            else {
                logger.error("Do not know how to deal with (down) area info from: "
                    + data.getLowerData());
            }
        }

        if (up == null && down != null) {
            area.setMode(StyledAreaSeriesCollection.FILL_MODE.ABOVE);
            down.setKey(seriesName);
            area.addSeries(down);
            area.addSeries(StyledSeriesBuilder.createGroundAtInfinity(down));
        }
        else if (up != null && down == null) {
            area.setMode(StyledAreaSeriesCollection.FILL_MODE.UNDER);
            area.addSeries(up);
            area.addSeries(StyledSeriesBuilder.createGroundAtInfinity(up));
        }
        else if (up != null && down != null) {
            if (data.doPaintBetween()) {
                area.setMode(StyledAreaSeriesCollection.FILL_MODE.BETWEEN);
            }
            else {
                area.setMode(StyledAreaSeriesCollection.FILL_MODE.ABOVE);
            }
            area.addSeries(up);
            area.addSeries(down);
        }
        // Add area to the respective axis.
        addAreaSeries(area, axisIdxForFacet(data.getRootFacetName()), visible);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org