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 :