Mercurial > dive4elements > river
diff flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java @ 3786:4adc35aa655c
merged flys-artifacts/2.9.1
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:47 +0200 |
parents | 6772e9f9b65f |
children | 0eca080fc162 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flys-artifacts/src/main/java/de/intevation/flys/exports/LongitudinalSectionGenerator.java Fri Sep 28 12:14:47 2012 +0200 @@ -0,0 +1,686 @@ +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 :