/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.legendgraphic; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.geoserver.wms.GetLegendGraphicRequest; import org.geoserver.wms.map.ImageUtils; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.renderer.i18n.ErrorKeys; import org.geotools.renderer.i18n.Errors; import org.geotools.renderer.lite.RendererUtilities; import org.geotools.renderer.lite.StreamingRenderer; import org.geotools.renderer.style.ExpressionExtractor; import org.geotools.styling.ColorMapEntry; import org.geotools.styling.Description; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.RasterSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.Style; import org.geotools.styling.Symbolizer; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.feature.type.PropertyType; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.opengis.style.ChannelSelection; import org.opengis.util.InternationalString; /** * Utility class for building legends, it exposes many methods that could be reused anywhere. * * <p> * I am not preventin people from subclassing this method so that they could add their own utility methods. * @author Simone Giannecchini, GeoSolutions SAS * */ @SuppressWarnings({"deprecation","unchecked"}) public class LegendUtils { /** * Ensures that the provided argument is not <code>null</code>. * <p> * If it <code>null</code> it must throw a {@link NullPointerException}. * * @param argument argument to check for <code>null</code>. */ protected static void ensureNotNull(final Object argument){ ensureNotNull(argument,"Argument cannot be null"); } /** * Ensures that the provided argument is not <code>null</code>. * <p> * If it <code>null</code> it must throw a {@link NullPointerException}. * * @param argument argument to check for <code>null</code>. * @param message leading message to print out in case the test fails. */ protected static void ensureNotNull(final Object argument,final String message){ if(message==null) throw new NullPointerException("Message cannot be null"); if(argument==null) throw new NullPointerException(message+" cannot be null"); } /** * Legend layouts */ public enum LegendLayout{ HORIZONTAL,VERTICAL; } public enum VAlign{ TOP,MIDDLE,BOTTOM; } public enum HAlign{ LEFT,CENTERED,RIGHT,JUSTIFIED; } /**Default {@link Font} name for legends.*/ public final static String DEFAULT_FONT_NAME="Sans-Serif"; /**Default channel name for {@link ChannelSelection} elements.*/ public final static String DEFAULT_CHANNEL_NAME="1"; /**Default {@link Font} for legends.*/ public final static int DEFAULT_FONT_TYPE= Font.PLAIN; /**Default {@link Font} for legends.*/ public final static int DEFAULT_FONT_SIZE= 12; /**Default {@link Font} for legends.*/ public final static Font DEFAULT_FONT= new Font("Sans-Serif",Font.PLAIN,12); /** * Default Legend graphics background color */ public static final Color DEFAULT_BG_COLOR = Color.WHITE; /** * Default label color */ public static final Color DEFAULT_FONT_COLOR = Color.BLACK; /** padding percentage factor at both sides of the legend. */ public static final float hpaddingFactor = 0.15f; /** top & bottom padding percentage factor for the legend */ public static final float vpaddingFactor = 0.15f; /** padding percentage factor at both sides of the legend. */ public static final float rowPaddingFactor = 0.15f; /** top & bottom padding percentage factor for the legend */ public static final float columnPaddingFactor = 0.15f; /** padding percentage factor at both sides of the legend. */ public static final float marginFactor = 0.015f; /** * default legend graphic layout is vertical */ private static final LegendLayout DEFAULT_LAYOUT = LegendLayout.VERTICAL; /** * default group legend graphic layout is vertical */ private static final LegendLayout DEFAULT_GROUPLAYOUT = LegendLayout.VERTICAL; /** * default column height is not limited */ private static final int DEFAULT_COLUMN_HEIGHT = 0; /** * default row width is not limited */ private static final int DEFAULT_ROW_WIDTH = 0; /** * default column number is not limited */ private static final int DEFAULT_COLUMNS = 0; /** * default row number is not limited */ private static final int DEFAULT_ROWS = 0; /** * shared package's logger */ private static final Logger LOGGER = org.geotools.util.logging.Logging .getLogger(LegendUtils.class.getPackage().getName()); public static final Color DEFAULT_BORDER_COLOR = Color.black; /** * Retrieves the legend layout from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract the {@link LegendLayout} information. * @return the {@link LegendLayout} specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_LAYOUT. * */ public static LegendLayout getLayout(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); LegendLayout layout = DEFAULT_LAYOUT; if (legendOptions != null && legendOptions.get("layout") != null) { try { layout = LegendLayout.valueOf(((String) legendOptions.get("layout")).toUpperCase()); } catch (IllegalArgumentException e) { } } return layout; } /** * Retrieves the group legend layout from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract the group {@link LegendLayout} information. * @return the group {@link LegendLayout} specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_LAYOUT. * */ public static LegendLayout getGroupLayout(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); LegendLayout layout = DEFAULT_LAYOUT; if (legendOptions != null && legendOptions.get("grouplayout") != null) { try { layout = LegendLayout.valueOf(((String) legendOptions.get("grouplayout")).toUpperCase()); } catch (IllegalArgumentException e) { } } return layout; } /** * Retrieves column height of legend from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract column height information. * @return the column height specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_COLUMN_HEIGHT. * */ public static int getColumnHeight(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); int columnheight = DEFAULT_COLUMN_HEIGHT; if (legendOptions != null && legendOptions.get("columnheight") != null) { try { columnheight = Integer.parseInt((String) legendOptions.get("columnheight")); } catch (NumberFormatException e) { } } return columnheight; } /** * Retrieves row width of legend from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract row width information. * @return the row width specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_ROW_WIDTH. * */ public static int getRowWidth(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); int rowwidth = DEFAULT_ROW_WIDTH; if (legendOptions != null && legendOptions.get("rowwidth") != null) { try { rowwidth = Integer.parseInt((String) legendOptions.get("rowwidth")); } catch (NumberFormatException e) { } } return rowwidth; } /** * Retrieves columns of legend from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract columns information. * @return the columns specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_COLUMNS. * */ public static int getColumns(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); int columns = DEFAULT_COLUMNS; if (legendOptions != null && legendOptions.get("columns") != null) { try { columns = Integer.parseInt((String) legendOptions.get("columns")); } catch (NumberFormatException e) { } } return columns; } /** * Retrieves rows of legend from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract rows information. * @return the rows specified in the provided {@link GetLegendGraphicRequest} or a default DEFAULT_ROWS. * */ public static int getRows(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String, Object> legendOptions = req.getLegendOptions(); int rows = DEFAULT_ROWS; if (legendOptions != null && legendOptions.get("rows") != null) { try { rows = Integer.parseInt((String) legendOptions.get("rows")); } catch (NumberFormatException e) { } } return rows; } /** * Retrieves the font from the provided {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract the {@link Font} information. * @return the {@link Font} specified in the provided {@link GetLegendGraphicRequest} or a default {@link Font}. * */ public static Font getLabelFont(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String,Object> legendOptions = req.getLegendOptions(); if(legendOptions==null) return DEFAULT_FONT; String legendFontName=LegendUtils.DEFAULT_FONT_NAME; if (legendOptions.get("fontName") != null) { legendFontName = (String) legendOptions.get("fontName"); } int legendFontFamily=LegendUtils.DEFAULT_FONT_TYPE; if (legendOptions.get("fontStyle") != null) { String legendFontFamily_ = (String) legendOptions.get("fontStyle"); if (legendFontFamily_.equalsIgnoreCase("italic")) { legendFontFamily= Font.ITALIC; } else if (legendFontFamily_.equalsIgnoreCase("bold")) { legendFontFamily= Font.BOLD; } } int legendFontSize=LegendUtils.DEFAULT_FONT_SIZE ; if (legendOptions.get("fontSize") != null) { try { legendFontSize = Integer.valueOf((String) legendOptions .get("fontSize")); } catch (NumberFormatException e) { LOGGER .warning("Error trying to interpret legendOption 'fontSize': "+ legendOptions.get("fontSize")); legendFontSize=LegendUtils.DEFAULT_FONT_SIZE; } } double dpi = RendererUtilities.getDpi(req.getLegendOptions()); double standardDpi = RendererUtilities.getDpi(Collections.emptyMap()); if (dpi != standardDpi) { double scaleFactor = dpi / standardDpi; legendFontSize = (int) Math.ceil(legendFontSize * scaleFactor); } if(legendFontFamily==LegendUtils.DEFAULT_FONT_TYPE&& legendFontName.equalsIgnoreCase(LegendUtils.DEFAULT_FONT_NAME)&& (legendFontSize==LegendUtils.DEFAULT_FONT_SIZE||legendFontSize<=0)) return DEFAULT_FONT; return new Font(legendFontName, legendFontFamily, legendFontSize); } /** * Extracts the Label {@link Font} {@link Color} from the provided {@link GetLegendGraphicRequest}. * * <p> * If there is no label {@link Font} specified a default {@link Font} {@link Color} will be provided. * * @param req the {@link GetLegendGraphicRequest} from which to extract label color information. * @return the Label {@link Font} {@link Color} extracted from the provided {@link GetLegendGraphicRequest} or * a default {@link Font} {@link Color}. */ public static Color getLabelFontColor(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String,Object> legendOptions = req.getLegendOptions(); final String color = legendOptions!=null?(String) legendOptions.get("fontColor"):null; if (color == null) { // return the default return DEFAULT_FONT_COLOR; } try { return color(color); } catch (NumberFormatException e) { if (LOGGER.isLoggable(Level.WARNING)) LOGGER.warning("Could not decode label color: " + color + ", default to " + DEFAULT_FONT_COLOR.toString()); return DEFAULT_FONT_COLOR; } } /** * Checks if the graphics should be text antialiasing * * @param req the {@link GetLegendGraphicRequest} from which to extract font antialiasing information. * * @return true if the fontAntiAliasing is set to on */ public static boolean isFontAntiAliasing(final GetLegendGraphicRequest req) { if (req.getLegendOptions().get("fontAntiAliasing") instanceof String) { String aaVal = (String) req.getLegendOptions().get("fontAntiAliasing"); if (aaVal.equalsIgnoreCase("on") || aaVal.equalsIgnoreCase("true") || aaVal.equalsIgnoreCase("yes") || aaVal.equalsIgnoreCase("1")) { return true; } } return false; } /** * Returns the image background color for the given * {@link GetLegendGraphicRequest}. * * @param req a {@link GetLegendGraphicRequest} from which we should extract the background color. * @return the Color for the hexadecimal value passed as the * <code>BGCOLOR</code> * {@link GetLegendGraphicRequest#getLegendOptions() legend option}, * or the default background color if no bgcolor were passed. */ public static Color getBackgroundColor(final GetLegendGraphicRequest req) { ensureNotNull(req, "GetLegendGraphicRequestre"); final Map<String,Object> legendOptions = req.getLegendOptions(); if(legendOptions==null) return DEFAULT_BG_COLOR; Object clr = legendOptions.get("bgColor"); if(clr instanceof Color) { return (Color) clr; } else if (clr == null) { // return the default return DEFAULT_BG_COLOR; } try { return color((String) clr); } catch (NumberFormatException e) { LOGGER.warning("Could not decode background color: " + clr + ", default to " + DEFAULT_BG_COLOR.toString()); return DEFAULT_BG_COLOR; } } /** * Extracts the opacity from the provided {@link ColorMapEntry}. * * <p> * 1.0 is returned in case the provided {@link ColorMapEntry} is null or invalid. * * @param entry * @return the opacity from the provided {@link ColorMapEntry} or 1.0 if something bad happens. * * @throws IllegalArgumentException * @throws MissingResourceException */ public static double getOpacity(final ColorMapEntry entry) throws IllegalArgumentException, MissingResourceException { ensureNotNull(entry, "ColorMapEntry"); // // // // As stated in <a // href="https://portal.opengeospatial.org/files/?artifact_id=1188"> // OGC Styled-Layer Descriptor Report (OGC 02-070) version // 1.0.0.</a>: // "Not all systems can support opacity in colormaps. The default // opacity is 1.0 (fully opaque)." // // // Expression opacity = entry.getOpacity(); Double opacityValue = null; if (opacity != null) opacityValue = opacity.evaluate(null, Double.class); else return 1.0; if(opacityValue == null && opacity instanceof Literal) { String opacityExp = opacity.evaluate(null, String.class); opacity = ExpressionExtractor.extractCqlExpressions(opacityExp); opacityValue = opacity.evaluate(null, Double.class); } if ((opacityValue.doubleValue() - 1) > 0 || opacityValue.doubleValue() < 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$2, "Opacity", opacityValue)); } return opacityValue.doubleValue(); } /** * Tries to decode the provided {@link String} into an HEX color definition in RRGGBB, 0xRRGGBB or * #RRGGBB format * * <p> * In case the {@link String} is not correct a {@link NumberFormatException} will be thrown. * * @param hex a {@link String} that should contain an Hexadecimal color representation. * @return a {@link Color} representing the provided {@link String}. * @throws NumberFormatException in case the string is badly formatted. */ public static Color color(String hex) { ensureNotNull(hex,"hex value"); if(hex.startsWith("0x")) { hex = hex.substring(2); } if (!hex.startsWith("#")) { hex = "#" + hex; } return Color.decode(hex); } /** * Get the {@link Color} out of this {@link ColorMapEntry}. * @param entry the {@link ColorMapEntry} from which to extract the {@link Color} component. * * @return the {@link Color} out of this {@link ColorMapEntry}. * @throws NumberFormatException in case the color string is badly formatted. */ public static Color color(final ColorMapEntry entry){ ensureNotNull(entry, "entry"); Expression color = entry.getColor(); ensureNotNull(color, "color"); String colorString= color.evaluate(null, String.class); if(colorString != null && colorString.startsWith("${")) { color = ExpressionExtractor.extractCqlExpressions(colorString); colorString = color.evaluate(null, String.class); } ensureNotNull(colorString, "colorString"); return color(colorString); } /** * Extracts the quantity part from the provided {@link ColorMapEntry}. * * @param entry the provided {@link ColorMapEntry} from which we should extract the quantity part. * @return the quantity part for the provided {@link ColorMapEntry}. */ public static double getQuantity(final ColorMapEntry entry) { ensureNotNull(entry, "entry"); Expression quantity = entry.getQuantity(); ensureNotNull(quantity, "quantity"); Double quantityString = quantity.evaluate(null, Double.class); if(quantityString == null && quantity instanceof Literal) { String quantityExp = quantity.evaluate(null, String.class); quantity = ExpressionExtractor.extractCqlExpressions(quantityExp); quantityString = quantity.evaluate(null, Double.class); } ensureNotNull(quantityString, "quantityString"); return quantityString.doubleValue(); } /** * Finds the applicable Rules for the given scale denominator. * * @param ftStyles * @param scaleDenominator * * @return an array of {@link Rule}s. */ public static Rule[] getApplicableRules(final FeatureTypeStyle[] ftStyles, double scaleDenominator) { ensureNotNull(ftStyles, "FeatureTypeStyle array "); /** * Holds both the rules that apply and the ElseRule's if any, in the * order they appear */ final List<Rule> ruleList = new ArrayList<Rule>(); // get applicable rules at the current scale for (int i = 0; i < ftStyles.length; i++) { FeatureTypeStyle fts = ftStyles[i]; Rule[] rules = fts.getRules(); for (int j = 0; j < rules.length; j++) { Rule r = rules[j]; if (isWithInScale(r, scaleDenominator)) { ruleList.add(r); /* * I'm commented this out since I guess it has no sense * for producing the legend, since whether or not the rule * has an else filter, the legend is drawn only if the * scale denominator lies inside the rule's scale range. if (r.hasElseFilter()) { ruleList.add(r); } */ } } } return ruleList.toArray(new Rule[ruleList.size()]); } /** * Checks if a rule can be triggered at the current scale level * * @param r The rule * @param scaleDenominator the scale denominator to check if it is between * the rule's scale range. -1 means that it allways is. * * @return true if the scale is compatible with the rule settings */ public static boolean isWithInScale(final Rule r,final double scaleDenominator) { return (scaleDenominator == -1) || (((r.getMinScaleDenominator() - BufferedImageLegendGraphicBuilder.TOLERANCE) <= scaleDenominator) && ((r.getMaxScaleDenominator() + BufferedImageLegendGraphicBuilder.TOLERANCE) > scaleDenominator)); } /** * Return a {@link BufferedImage} representing this label. * The characters '\n' '\r' and '\f' are interpreted as linebreaks, * as is the characater combination "\n" (as opposed to the actual '\n' character). * This allows people to force line breaks in their labels by * including the character "\" followed by "n" in their * label. * * @param label - the label to render * @param g - the Graphics2D that will be used to render this label * @return a {@link BufferedImage} of the properly rendered label. */ public static BufferedImage renderLabel(final String label,final Graphics2D g,final GetLegendGraphicRequest req) { ensureNotNull(label); ensureNotNull(g); ensureNotNull(req); // We'll accept '/n' as a text string //to indicate a line break, as well as a traditional 'real' line-break in the XML. BufferedImage renderedLabel; Color labelColor = getLabelFontColor(req); if ((label.indexOf("\n") != -1) || (label.indexOf("\\n") != -1)) { //this is a label WITH line-breaks...we need to figure out it's height *and* //width, and then adjust the legend size accordingly Rectangle2D bounds = new Rectangle2D.Double(0, 0, 0, 0); ArrayList<Integer> lineHeight = new ArrayList<Integer>(); // four backslashes... "\\" -> '\', so "\\\\n" -> '\' + '\' + 'n' final String realLabel = label.replaceAll("\\\\n", "\n"); StringTokenizer st = new StringTokenizer(realLabel, "\n\r\f"); while (st.hasMoreElements()) { final String token = st.nextToken(); Rectangle2D thisLineBounds = g.getFontMetrics().getStringBounds(token, g); //if this is directly added as thisLineBounds.getHeight(), then there are rounding errors //because we can only DRAW fonts at discrete integer coords. final int thisLineHeight = (int) Math.ceil(thisLineBounds.getHeight()); bounds.add(0, thisLineHeight + bounds.getHeight()); bounds.add(thisLineBounds.getWidth(), 0); lineHeight.add((int) Math.ceil(thisLineBounds.getHeight())); } //make the actual label image renderedLabel = new BufferedImage((int) Math.ceil(bounds.getWidth()), (int) Math.ceil(bounds.getHeight()), BufferedImage.TYPE_INT_ARGB); st = new StringTokenizer(realLabel, "\n\r\f"); Graphics2D rlg = renderedLabel.createGraphics(); rlg.setColor(labelColor); rlg.setFont(g.getFont()); rlg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); int y = 0 - g.getFontMetrics().getDescent(); int c = 0; while (st.hasMoreElements()) { y += lineHeight.get(c++).intValue(); rlg.drawString(st.nextToken(), 0, y); } rlg.dispose(); } else { //this is a traditional 'regular-old' label. Just figure the //size and act accordingly. int height = (int) Math.ceil(g.getFontMetrics().getStringBounds(label, g).getHeight()); int width = (int) Math.ceil(g.getFontMetrics().getStringBounds(label, g).getWidth()); renderedLabel = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D rlg = renderedLabel.createGraphics(); rlg.setColor(labelColor); rlg.setFont(g.getFont()); rlg.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, g.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); rlg.drawString(label, 0, height - rlg.getFontMetrics().getDescent()); rlg.dispose(); } return renderedLabel; } /** * This method tries to merge horizontally 3 {@link BufferedImage}. The first one must be not null, the others can be null. * * @param left first {@link BufferedImage} to merge. * @param center second {@link BufferedImage} to merge. * @param right third {@link BufferedImage} to merge. * @param hintsMap hints to use for drawing * @param dx buffer between images * @param transparent tells me whether or not the bkg should be transparent * @param backgroundColor the background color * @return a {@link BufferedImage} for the union of the provided images. */ public static BufferedImage hMergeBufferedImages( final BufferedImage left, final BufferedImage center, final BufferedImage right, final Map<Key, Object> hintsMap, final boolean transparent, final Color backgroundColor, final double dx) { if(right==null&¢er==null) return left; if(left==null) throw new NullPointerException("Left image cannot be null"); int totalHeight = (int) (Math.max(left.getHeight(),Math.max((center!=null?center.getHeight():Double.NEGATIVE_INFINITY), right!=null?right.getHeight():0))+0.5); final int totalWidth = (int) (left.getWidth() +(center!=null? center.getWidth():0)+(right!=null?right.getWidth():0)+2*dx+0.5); final BufferedImage finalImage = ImageUtils.createImage(totalWidth, totalHeight, (IndexColorModel)null, transparent); final Graphics2D finalGraphics = ImageUtils.prepareTransparency(transparent, backgroundColor, finalImage, hintsMap); //place the left element int offsetX=0; finalGraphics.drawImage(left, offsetX,0,null); ///place the central element offsetX=(int) (left.getWidth()+dx+0.5); if(center!=null){ finalGraphics.drawImage(center, offsetX,0,null); offsetX+=(int) (center.getWidth()+dx+0.5); } ///place the right element in case we have it if(right!=null){ finalGraphics.drawImage(right, offsetX,0,null); } finalGraphics.dispose(); return (BufferedImage) finalImage; } /** * Checks if the provided {@link FeatureType} contains a coverage as per used by the {@link StreamingRenderer}. * * @param layer a {@link FeatureType} to check if it contains a grid. * @return <code>true</code> if this layer contains a gridcoverage, <code>false</code> otherwise. */ public static boolean checkGridLayer(final FeatureType layer) { if(!(layer instanceof SimpleFeatureType)) return false; boolean found=false; final Collection<PropertyDescriptor> descriptors = layer.getDescriptors(); for(PropertyDescriptor descriptor: descriptors){ //get the type final PropertyType type=descriptor.getType(); if(type.getBinding().isAssignableFrom(GridCoverage2D.class)||type.getBinding().isAssignableFrom(GridCoverage2DReader.class)) { found=true; break; } } return found; } /** * Checks if the provided style contains at least one {@link RasterSymbolizer} */ public static boolean checkRasterSymbolizer(final Style style) { for (FeatureTypeStyle fts : style.featureTypeStyles()) { for (Rule r : fts.rules()) { for (Symbolizer s : r.symbolizers()) { if (s instanceof RasterSymbolizer) { return true; } } } } return false; } /** * Locates the specified rule by name * @param fts * @param rule * */ public static Rule getRule(FeatureTypeStyle[] fts, String rule) { Rule sldRule = null; for (int i = 0; i < fts.length; i++) { Rule[] rules = fts[i].getRules(); for (int r = 0; r < rules.length; r++) { if (rule.equalsIgnoreCase(rules[r].getName())) { sldRule = rules[r]; if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(new StringBuffer("found requested rule: ").append(rule) .toString()); } break; } } } return sldRule; } static String getRuleLabel(Rule rule, GetLegendGraphicRequest req) { // What's the label on this rule? We prefer to use // the 'title' if it's available, but fall-back to 'name' final Description description = rule.getDescription(); String label = ""; if (description != null && description.getTitle() != null) { final InternationalString title = description.getTitle(); if (req.getLocale() != null) { label = title.toString(req.getLocale()); } else { label = title.toString(); } } else if (rule.getName() != null) { label = rule.getName(); } return label; } }