teichmann@5863: /* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
teichmann@5863: * Software engineering by Intevation GmbH
teichmann@5863: *
teichmann@5994: * This file is Free Software under the GNU AGPL (>=v3)
teichmann@5863: * and comes with ABSOLUTELY NO WARRANTY! Check out the
teichmann@5994: * documentation coming with Dive4Elements River for details.
teichmann@5863: */
teichmann@5863:
teichmann@5831: package org.dive4elements.river.exports;
ingo@299:
felix@6887: import java.util.ArrayList;
felix@6887: import java.util.List;
felix@6887:
gernotbelger@9312: import org.apache.log4j.Logger;
teichmann@5831: import org.dive4elements.artifactdatabase.state.ArtifactAndFacet;
gernotbelger@9312: import org.dive4elements.artifactdatabase.state.State;
gernotbelger@9312: import org.dive4elements.artifacts.CallContext;
teichmann@5867: import org.dive4elements.river.artifacts.D4EArtifact;
gernotbelger@9312: import org.dive4elements.river.artifacts.GaugeDischargeCurveArtifact;
tom@8726: import org.dive4elements.river.artifacts.access.RiverAccess;
teichmann@5831: import org.dive4elements.river.artifacts.model.FacetTypes;
teichmann@5831: import org.dive4elements.river.artifacts.model.WQKms;
rrenkert@8181: import org.dive4elements.river.exports.process.MiscDischargeProcessor;
gernotbelger@9312: import org.dive4elements.river.jfree.Bounds;
felix@6887: import org.dive4elements.river.jfree.CollisionFreeXYTextAnnotation;
aheinecke@7691: import org.dive4elements.river.jfree.DoubleBounds;
teichmann@5864: import org.dive4elements.river.jfree.RiverAnnotation;
felix@6453: import org.dive4elements.river.jfree.StickyAxisAnnotation;
teichmann@5831: import org.dive4elements.river.jfree.StyledXYSeries;
teichmann@5831: import org.dive4elements.river.model.Gauge;
teichmann@5831: import org.dive4elements.river.model.River;
teichmann@6905: import org.dive4elements.river.themes.ThemeDocument;
felix@6453: import org.jfree.chart.annotations.XYTextAnnotation;
ingo@728: import org.jfree.chart.axis.ValueAxis;
ingo@375: import org.jfree.chart.plot.XYPlot;
ingo@728: import org.jfree.data.Range;
ingo@923: import org.jfree.data.xy.XYSeries;
felix@1103:
ingo@299: /**
ingo@299: * An OutGenerator that generates discharge curves.
ingo@299: *
ingo@299: * @author Ingo Weinzierl
ingo@299: */
gernotbelger@9312: public class DischargeCurveGenerator extends XYChartGenerator implements FacetTypes {
ingo@299:
felix@6882: /** Beware, in this implementation, the W axis is also in cm! */
felix@1933: public static enum YAXIS {
gernotbelger@9312: WCm(0), W(1);
felix@1933: protected int idx;
gernotbelger@9312:
gernotbelger@9312: private YAXIS(final int c) {
gernotbelger@9312: this.idx = c;
felix@1933: }
felix@1933: }
felix@1933:
teichmann@8202: /** The log used in this generator. */
gernotbelger@9312: private static Logger log = Logger.getLogger(DischargeCurveGenerator.class);
ingo@414:
gernotbelger@9312: public static final String I18N_CHART_TITLE = "chart.discharge.curve.title";
ingo@408:
gernotbelger@9312: public static final String I18N_CHART_SUBTITLE = "chart.discharge.curve.subtitle";
ingo@408:
gernotbelger@9312: public static final String I18N_XAXIS_LABEL = "common.export.csv.header.q";
ingo@408:
gernotbelger@9312: public static final String I18N_YAXIS_LABEL = "chart.discharge.curve.yaxis.label";
gernotbelger@9312:
gernotbelger@9312: public static final String I18N_CHART_TITLE_DEFAULT = "Abflusskurven";
gernotbelger@9312: public static final String I18N_XAXIS_LABEL_DEFAULT = "Q [m\u00b3/s]";
gernotbelger@9312: public static final String I18N_YAXIS_LABEL_DEFAULT = "W [cm]";
ingo@408:
felix@6445: /**
felix@6445: * Returns the PNP (Datum) of gauge, if at gauge, 0 otherwise.
felix@6445: */
gernotbelger@9312: public static double getCurrentGaugeDatum(final double km, final D4EArtifact artifact, final double tolerance) {
felix@6739: // Look if there is a gauge at chosen km:
felix@6739: // Get gauge which is defined for km
gernotbelger@9312: final Gauge gauge = new RiverAccess(artifact).getRiver().determineGaugeAtStation(km);
felix@6939: if (gauge == null) {
teichmann@8202: log.error("No Gauge could be found at station " + km + "!");
felix@6939: return 0d;
felix@6939: }
felix@6445: double subtractPNP = 0d;
felix@6739: // Compare to km.
felix@6463: if (Math.abs(km - gauge.getStation().doubleValue()) < tolerance) {
felix@6445: subtractPNP = gauge.getDatum().doubleValue();
felix@6445: }
felix@6445: return subtractPNP;
felix@6445: }
felix@6445:
felix@6882: /** Get the current Gauge datum with default distance tolerance. */
felix@6445: public double getCurrentGaugeDatum() {
gernotbelger@9312: return getCurrentGaugeDatum(getRange()[0], (D4EArtifact) getMaster(), 1e-4);
felix@6445: }
felix@6445:
felix@6559: /** Overriden to show second axis also if no visible data present. */
felix@6559: @Override
gernotbelger@9312: protected void adjustAxes(final XYPlot plot) {
felix@6559: super.adjustAxes(plot);
aheinecke@7701: // XXX Hacking around that there were two axes shown in official Gauge
aheinecke@7701: // Discharge, the one from the WINFO module.
aheinecke@7701: // This should be made unecessary in a Q Diagram refactoring with
aheinecke@7701: // decent inheritance.
aheinecke@7701: if (getMaster() instanceof GaugeDischargeCurveArtifact) {
gernotbelger@9312: final GaugeDischargeCurveArtifact myMaster = (GaugeDischargeCurveArtifact) getMaster();
gernotbelger@9312: final State state = myMaster.getCurrentState(getContext());
gernotbelger@9123: if (GaugeDischargeCurveArtifact.STATIC_STATE_NAME.equals(state.getID())) {
aheinecke@7701: return;
aheinecke@7701: }
aheinecke@7701: }
aheinecke@7701: // End Hack
aheinecke@7701:
felix@6559: if (getCurrentGaugeDatum() != 0d) {
felix@6559: // Show the W[*m] axis even if there is no data.
felix@6559: plot.setRangeAxis(1, createYAxis(YAXIS.W.idx));
aheinecke@7691: syncWAxisRanges();
felix@6559: }
felix@6559: }
felix@6559:
aheinecke@7691: protected void syncWAxisRanges() {
aheinecke@7691: // Syncronizes the ranges of both W Axes to make sure
aheinecke@7691: // that the Data matches for both axes.
aheinecke@7691: Bounds boundsInMGauge = getYBounds(YAXIS.W.idx);
aheinecke@7691: Bounds boundsInCM = getYBounds(YAXIS.WCm.idx);
aheinecke@7691:
aheinecke@7701: if (boundsInMGauge == null || boundsInCM == null) {
aheinecke@7701: // One axis does not exist. Nothing to sync
aheinecke@7701: return;
aheinecke@7701: }
aheinecke@7701:
aheinecke@7691: // XXX Q-Symetry: I am assuming here that there can only
aheinecke@7691: // be a fixed Range for WinM as this is currently the only
aheinecke@7691: // thing that is configureable.
gernotbelger@9312: final Range fixedWinMRange = getRangeForAxisFromSettings(getYAxisWalker().getId(YAXIS.W.idx));
aheinecke@7691:
aheinecke@7691: // The combination of Range and Bounds is crazy..
aheinecke@7691: if (fixedWinMRange != null) {
gernotbelger@9312: boundsInMGauge = new DoubleBounds(fixedWinMRange.getLowerBound(), fixedWinMRange.getUpperBound());
aheinecke@7691: }
aheinecke@7691:
gernotbelger@9312: log.debug("Syncing Axis Bounds. Bounds W: " + boundsInMGauge.toString() + " Bounds Wcm: " + boundsInCM.toString());
aheinecke@7691:
gernotbelger@9312: final double datum = getCurrentGaugeDatum();
aheinecke@7691:
aheinecke@7691: // Convert boundsInMGauge to Datum+cm
gernotbelger@9312: final double convertedLower = ((Double) boundsInMGauge.getLower() - datum) * 100;
gernotbelger@9312: final double convertedUpper = ((Double) boundsInMGauge.getUpper() - datum) * 100;
gernotbelger@9312: final Bounds convertedBounds = new DoubleBounds(convertedLower, convertedUpper);
aheinecke@7691:
aheinecke@7691: // Now combine both Ranges
aheinecke@7691: boundsInCM = boundsInCM.combine(convertedBounds);
aheinecke@7691:
aheinecke@7691: // Recalculate absolute bounds
gernotbelger@9312: boundsInMGauge = new DoubleBounds((Double) boundsInCM.getLower() / 100d + datum, (Double) boundsInCM.getUpper() / 100d + datum);
aheinecke@7691:
aheinecke@7691: // Set the new combined bounds
aheinecke@7691: setYBounds(YAXIS.W.idx, boundsInMGauge);
aheinecke@7691: setYBounds(YAXIS.WCm.idx, boundsInCM);
gernotbelger@9312: log.debug("Synced Bounds W: " + boundsInMGauge.toString() + " Bounds Wcm: " + boundsInCM.toString());
aheinecke@7691: }
felix@6559:
ingo@299: public DischargeCurveGenerator() {
ingo@348: super();
ingo@299: }
ingo@299:
ingo@2000: @Override
ingo@2000: protected YAxisWalker getYAxisWalker() {
ingo@2000: return new YAxisWalker() {
ingo@2000: @Override
ingo@2000: public int length() {
ingo@2000: return YAXIS.values().length;
ingo@2000: }
ingo@2000:
ingo@2000: @Override
gernotbelger@9312: public String getId(final int idx) {
gernotbelger@9312: final YAXIS[] yaxes = YAXIS.values();
ingo@2000: return yaxes[idx].toString();
ingo@2000: }
ingo@2000: };
ingo@2000: }
ingo@2000:
christian@3409: /**
christian@3409: * Returns always null to suppress subtitles.
christian@3409: */
ingo@2048: @Override
gernotbelger@9123: protected String getDefaultChartTitle(final CallContext context) {
christian@3409: return null;
ingo@414: }
ingo@414:
ingo@2051: @Override
gernotbelger@9123: protected String getDefaultXAxisLabel(final CallContext context) {
ingo@408: return msg(I18N_XAXIS_LABEL, I18N_XAXIS_LABEL_DEFAULT);
ingo@369: }
ingo@369:
ingo@2051: @Override
gernotbelger@9312: protected String getDefaultYAxisLabel(final int pos) {
ingo@408: return msg(I18N_YAXIS_LABEL, I18N_YAXIS_LABEL_DEFAULT);
ingo@369: }
ingo@369:
felix@2302: /* TODO is this one really needed? */
ingo@728: @Override
gernotbelger@9312: protected boolean zoomX(final XYPlot plot, final ValueAxis axis, final Bounds bounds, final Range x) {
gernotbelger@9312: final boolean zoomin = super.zoom(plot, axis, bounds, x);
ingo@728:
ingo@728: if (!zoomin) {
ingo@728: axis.setLowerBound(0d);
ingo@728: }
ingo@728:
ingo@728: return zoomin;
ingo@728: }
ingo@728:
felix@6526: /** Translate River annotations if a gauge. */
gernotbelger@9312: public void translateRiverAnnotation(final RiverAnnotation riverAnnotation) {
felix@6453: if (getCurrentGaugeDatum() == 0d) {
felix@6453: return;
felix@6453: }
teichmann@8202: log.debug("Translate some river annotation.");
gernotbelger@9312: final double translate = getCurrentGaugeDatum();
gernotbelger@9312: final double factor = 100d;
gernotbelger@9312: for (final StickyAxisAnnotation annotation : riverAnnotation.getAxisTextAnnotations()) {
felix@6453: if (!annotation.atX()) {
gernotbelger@9312: annotation.setPos((annotation.getPos() - translate) * factor);
felix@6453: }
felix@6453: }
gernotbelger@9312: for (final XYTextAnnotation annotation : riverAnnotation.getTextAnnotations()) {
gernotbelger@9312: annotation.setY((annotation.getY() - translate) * factor);
felix@6453: }
felix@6453: }
felix@6453:
christian@3409: @Override
gernotbelger@9312: public void doOut(final ArtifactAndFacet artifactFacet, final ThemeDocument theme, final boolean visible) {
gernotbelger@9312: final String name = artifactFacet.getFacetName();
teichmann@8202: log.debug("DischargeCurveGenerator.doOut: " + name);
ingo@299:
gernotbelger@9123: final CallContext context = getContext();
gernotbelger@9312:
gernotbelger@9312: final MiscDischargeProcessor dProcessor = new MiscDischargeProcessor(getRange()[0]);
felix@6899: if (dProcessor.canHandle(name)) {
felix@6899: // In Base DischargeCurveGenerator, always at gauge, use WCm axis.
gernotbelger@9312: dProcessor.doOut(this, artifactFacet, theme, visible, YAXIS.WCm.idx);
gernotbelger@9312: } else if (name.equals(DISCHARGE_CURVE) || name.equals(GAUGE_DISCHARGE_CURVE)) {
gernotbelger@9556: doDischargeOut(artifactFacet.getFacetName(),(D4EArtifact) artifactFacet.getArtifact(), artifactFacet.getData(context), artifactFacet.getFacetDescription(), theme, visible);
gernotbelger@9312: } else if (FacetTypes.IS.MANUALPOINTS(name)) {
gernotbelger@9312: doPoints(artifactFacet.getData(context), artifactFacet, theme, visible, YAXIS.W.idx);
gernotbelger@9312: } else if (STATIC_WQ.equals(name)) {
gernotbelger@9312: doWQOut(artifactFacet.getData(context), artifactFacet, theme, visible);
gernotbelger@9312: } else {
gernotbelger@9312: log.warn("DischargeCurveGenerator.doOut: Unknown facet name: " + name);
gernotbelger@9312: return;
felix@1812: }
felix@1812: }
felix@1812:
felix@1812: /**
felix@1812: * Add series with discharge curve to diagram.
gernotbelger@9556: * @param string
felix@1812: */
gernotbelger@9556: protected void doDischargeOut(String facetName, final D4EArtifact artifact, final Object o, final String description, final ThemeDocument theme, final boolean visible) {
teichmann@8202: log.debug("DischargeCurveGenerator.doDischargeOut");
gernotbelger@9312: final WQKms wqkms = (WQKms) o;
sascha@721:
gernotbelger@9312: final String gaugeName = wqkms.getName();
sascha@721:
gernotbelger@9312: final River river = new RiverAccess(artifact).getRiver();
sascha@721:
sascha@721: if (river == null) {
teichmann@8202: log.debug("no river found");
sascha@721: return;
ingo@299: }
ingo@299:
gernotbelger@9312: final Gauge gauge = river.determineGaugeByName(gaugeName);
ingo@299:
sascha@721: if (gauge == null) {
teichmann@8202: log.debug("no gauge found");
sascha@721: return;
sascha@721: }
ingo@454:
gernotbelger@9556: final XYSeries series = new StyledXYSeries(facetName, description, theme);
ingo@923:
felix@1812: StyledSeriesBuilder.addPointsQW(series, wqkms);
ingo@923:
felix@1933: addAxisSeries(series, YAXIS.W.idx, visible);
ingo@299: }
felix@6883:
felix@6883: /**
felix@6883: * Add W/Q-Series to plot.
gernotbelger@9312: *
gernotbelger@9312: * @param wqkms
gernotbelger@9312: * actual data
gernotbelger@9312: * @param theme
gernotbelger@9312: * theme to use.
felix@6883: */
gernotbelger@9312: protected void doQOut(final Object wqkms, final ArtifactAndFacet aaf, final ThemeDocument theme, final boolean visible) {
teichmann@8202: log.debug("DischargeCurveGenerator: doQOut (add W/Q data).");
gernotbelger@9556: final XYSeries series = new StyledXYSeries(aaf.getFacetName(), aaf.getFacetDescription(), theme);
felix@6883:
felix@6883: StyledSeriesBuilder.addPointsQW(series, (WQKms) wqkms);
felix@6883:
felix@6883: addAxisSeries(series, YAXIS.W.idx, visible);
felix@6883: }
felix@6883:
felix@6887: /** Add a point annotation at given x and y coordinates. */
gernotbelger@9312: protected void addPointTextAnnotation(final String title, final double x, final double y, final ThemeDocument theme) {
gernotbelger@9123: final List textAnnos = new ArrayList<>();
gernotbelger@9312: final XYTextAnnotation anno = new CollisionFreeXYTextAnnotation(title, x, y);
felix@6887: textAnnos.add(anno);
gernotbelger@9312: final RiverAnnotation flysAnno = new RiverAnnotation(null, null, null, theme);
felix@6887: flysAnno.setTextAnnotations(textAnnos);
felix@6887: addAnnotations(flysAnno);
felix@6887: }
felix@6887:
felix@6883: /**
felix@6981: * Return true if all values in data[0] are smaller than zero
felix@6981: * (in imported data they are set to -1 symbolically).
felix@6889: * Return false if data is null or empty
felix@6889: */
gernotbelger@9312: private static boolean hasNoDischarge(final double[][] data) {
felix@6889: if (data == null || data.length == 0) {
felix@6889: return false;
felix@6889: }
felix@6889:
gernotbelger@9312: final double[] qs = data[0];
gernotbelger@9312: for (final double q : qs) {
felix@6981: if (q > 0d) {
felix@6981: return false;
felix@6889: }
felix@6889: }
felix@6889:
felix@6981: return true;
felix@6889: }
felix@6889:
felix@6889: /**
felix@6883: * Add WQ Data to plot.
gernotbelger@9312: *
gernotbelger@9312: * @param wq
gernotbelger@9312: * data as double[][]
felix@6883: */
gernotbelger@9312: protected void doWQOut(final Object wq, final ArtifactAndFacet aaf, final ThemeDocument theme, final boolean visible) {
teichmann@8202: log.debug("DischargeCurveGenerator: doWQOut");
gernotbelger@9312: final double[][] data = (double[][]) wq;
gernotbelger@9312: final String title = aaf.getFacetDescription();
felix@6883:
gernotbelger@9312: final double translate = getCurrentGaugeDatum();
felix@6889:
felix@6981: // If no Q values (i.e. all -1) found, add annotations.
felix@6981: if (hasNoDischarge(data)) {
gernotbelger@9123: final List xy = new ArrayList<>();
felix@6889:
gernotbelger@9312: for (double y : data[1]) {
felix@6889: if (translate != 0d) {
gernotbelger@9312: y = (y - translate) * 100d;
felix@6889: }
felix@6889:
gernotbelger@9312: xy.add(new StickyAxisAnnotation(title, (float) y, StickyAxisAnnotation.SimpleAxis.Y_AXIS));
felix@6889: }
felix@6889:
gernotbelger@9312: doAnnotations(new RiverAnnotation(title, xy), aaf, theme, visible);
felix@6889: return;
felix@6889: }
felix@6889:
felix@6889: // Otherwise add points.
gernotbelger@9556: final XYSeries series = new StyledXYSeries(aaf.getFacetName(),title, theme);
felix@6883:
felix@6883: if (translate != 0d) {
felix@6883: StyledSeriesBuilder.addPointsQW(series, data, -translate, 100d);
felix@6883: addAxisSeries(series, YAXIS.W.idx, visible);
gernotbelger@9312: } else {
felix@6883: StyledSeriesBuilder.addPoints(series, data, true);
felix@6883: addAxisSeries(series, YAXIS.W.idx, visible);
felix@6883: }
felix@6887:
gernotbelger@9312: if (visible && theme.parseShowPointLabel() && data != null && data.length != 0) {
felix@6887:
gernotbelger@9312: final double[] xs = data[0];
gernotbelger@9312: final double[] ys = data[1];
felix@6887: for (int i = 0; i < xs.length; i++) {
gernotbelger@9312: final double x = xs[i];
felix@6887: double y = ys[i];
felix@6887:
felix@6887: if (translate != 0d) {
gernotbelger@9312: y = (y - translate) * 100d;
felix@6887: }
felix@6887:
felix@6887: addPointTextAnnotation(title, x, y, theme);
felix@6887: }
felix@6887: }
felix@6883: }
ingo@299: }
ingo@299: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :