/** * Extracts legend for layer based on LayerInfo configuration or style LegendGraphics. * * @param published FeatureType representing the layer * @param w width for the image (hint) * @param h height for the image (hint) * @param transparent (should the image be transparent) * @param request GetLegendGraphicRequest being built * @return image with the title */ private RenderedImage getLayerLegend( LegendRequest legend, int w, int h, boolean transparent, GetLegendGraphicRequest request) { LegendInfo legendInfo = legend.getLegendInfo(); if (legendInfo == null) { return null; // nothing provided will need to dynamically generate } String onlineResource = legendInfo.getOnlineResource(); if (onlineResource == null || onlineResource.isEmpty()) { return null; // nothing provided will need to dynamically generate } URL url = null; try { url = new URL(onlineResource); } catch (MalformedURLException invalid) { LOGGER.fine("Unable to obtain " + onlineResource); return null; // should log this! } try { BufferedImage image = ImageIO.read(url); if (image.getWidth() == w && image.getHeight() == h) { return image; } final BufferedImage rescale = ImageUtils.createImage(w, h, (IndexColorModel) null, true); Graphics2D g = (Graphics2D) rescale.getGraphics(); g.setColor(new Color(255, 255, 255, 0)); g.fillRect(0, 0, w, h); double aspect = ((double) h) / ((double) image.getHeight()); int legendWidth = (int) (aspect * ((double) image.getWidth())); g.drawImage(image, 0, 0, legendWidth, h, null); g.dispose(); return rescale; } catch (IOException notFound) { LOGGER.log(Level.FINE, "Unable to legend graphic:" + url, notFound); return null; // unable to access image } }
/** * Receives a list of <code>BufferedImages</code> and produces a new one which holds all the * images in <code>imageStack</code> one above the other, handling labels. * * @param imageStack the list of BufferedImages, one for each applicable Rule * @param rules The applicable rules, one for each image in the stack (if not null it's used to * compute labels) * @param request The request. * @param forceLabelsOn true for force labels on also with a single image. * @param forceLabelsOff true for force labels off also with more than one rule. * @return the stack image with all the images on the argument list. * @throws IllegalArgumentException if the list is empty */ private BufferedImage mergeLegends( List<RenderedImage> imageStack, Rule[] rules, GetLegendGraphicRequest req, boolean forceLabelsOn, boolean forceLabelsOff) { Font labelFont = LegendUtils.getLabelFont(req); boolean useAA = LegendUtils.isFontAntiAliasing(req); if (imageStack.size() == 0) { return null; } final BufferedImage finalLegend; if (imageStack.size() == 1 && (!forceLabelsOn || rules == null)) { finalLegend = (BufferedImage) imageStack.get(0); } else { final int imgCount = imageStack.size(); final String[] labels = new String[imgCount]; BufferedImage img = ((BufferedImage) imageStack.get(0)); int totalHeight = 0; int totalWidth = 0; int[] rowHeights = new int[imgCount]; BufferedImage labelsGraphics[] = new BufferedImage[imgCount]; for (int i = 0; i < imgCount; i++) { img = (BufferedImage) imageStack.get(i); if (forceLabelsOff || rules == null) { totalWidth = (int) Math.ceil(Math.max(img.getWidth(), totalWidth)); rowHeights[i] = img.getHeight(); totalHeight += img.getHeight(); } else { Rule rule = rules[i]; // 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(); Locale locale = req.getLocale(); if (description != null && description.getTitle() != null) { final InternationalString title = description.getTitle(); if (locale != null) { labels[i] = title.toString(locale); } else { labels[i] = title.toString(); } } else if (rule.getName() != null) { labels[i] = rule.getName(); } else { labels[i] = ""; } if (labels[i] != null && labels[i].length() > 0) { final BufferedImage renderedLabel = getRenderedLabel(img, labels[i], req); labelsGraphics[i] = renderedLabel; final Rectangle2D bounds = new Rectangle2D.Double(0, 0, renderedLabel.getWidth(), renderedLabel.getHeight()); totalWidth = (int) Math.ceil(Math.max(img.getWidth() + bounds.getWidth(), totalWidth)); rowHeights[i] = (int) Math.ceil(Math.max(img.getHeight(), bounds.getHeight())); } else { totalWidth = (int) Math.ceil(Math.max(img.getWidth(), totalWidth)); rowHeights[i] = (int) Math.ceil(img.getHeight()); labelsGraphics[i] = null; } totalHeight += rowHeights[i]; } } // buffer the width a bit totalWidth += 2; final boolean transparent = req.isTransparent(); final Color backgroundColor = LegendUtils.getBackgroundColor(req); final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>(); // create the final image finalLegend = ImageUtils.createImage(totalWidth, totalHeight, (IndexColorModel) null, transparent); Graphics2D finalGraphics = ImageUtils.prepareTransparency(transparent, backgroundColor, finalLegend, hintsMap); int topOfRow = 0; for (int i = 0; i < imgCount; i++) { img = (BufferedImage) imageStack.get(i); // draw the image int y = topOfRow; if (img.getHeight() < rowHeights[i]) { // move the image to the center of the row y += (int) ((rowHeights[i] - img.getHeight()) / 2d); } finalGraphics.drawImage(img, 0, y, null); if (forceLabelsOff || rules == null) { topOfRow += rowHeights[i]; continue; } finalGraphics.setFont(labelFont); if (useAA) { finalGraphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } else { finalGraphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } // draw the label if (labels[i] != null && labels[i].length() > 0) { // first create the actual overall label image. final BufferedImage renderedLabel = labelsGraphics[i]; y = topOfRow; if (renderedLabel.getHeight() < rowHeights[i]) { y += (int) ((rowHeights[i] - renderedLabel.getHeight()) / 2d); } finalGraphics.drawImage(renderedLabel, img.getWidth(), y, null); // cleanup renderedLabel.flush(); labelsGraphics[i] = null; } topOfRow += rowHeights[i]; } finalGraphics.dispose(); } return finalLegend; }
/** * Renders a title for a layer (to be put on top of the layer legend). * * @param legend FeatureType representing the layer * @param w width for the image (hint) * @param h height for the image (hint) * @param transparent (should the image be transparent) * @param request GetLegendGraphicRequest being built * @return image with the title */ private RenderedImage getLayerTitle( LegendRequest legend, int w, int h, boolean transparent, GetLegendGraphicRequest request) { String title = legend.getTitle(); final BufferedImage image = ImageUtils.createImage(w, h, (IndexColorModel) null, transparent); return getRenderedLabel(image, title, request); }
/** * Takes a GetLegendGraphicRequest and produces a BufferedImage that then can be used by a * subclass to encode it to the appropriate output format. * * @param request the "parsed" request, where "parsed" means that it's values are already * validated so this method must not take care of verifying the requested layer exists and the * like. * @throws ServiceException if there are problems creating a "sample" feature instance for the * FeatureType <code>request</code> returns as the required layer (which should not occur). */ public BufferedImage buildLegendGraphic(GetLegendGraphicRequest request) throws ServiceException { // list of images to be rendered for the layers (more than one if // a layer group is given) List<RenderedImage> layersImages = new ArrayList<RenderedImage>(); List<LegendRequest> layers = request.getLegends(); // List<FeatureType> layers=request.getLayers(); // List<Style> styles=request.getStyles(); // List<String> rules=request.getRules(); boolean forceLabelsOn = false; boolean forceLabelsOff = false; if (request.getLegendOptions().get("forceLabels") instanceof String) { String forceLabelsOpt = (String) request.getLegendOptions().get("forceLabels"); if (forceLabelsOpt.equalsIgnoreCase("on")) { forceLabelsOn = true; } else if (forceLabelsOpt.equalsIgnoreCase("off")) { forceLabelsOff = true; } } boolean forceTitlesOff = false; if (request.getLegendOptions().get("forceTitles") instanceof String) { String forceTitlesOpt = (String) request.getLegendOptions().get("forceTitles"); if (forceTitlesOpt.equalsIgnoreCase("off")) { forceTitlesOff = true; } } for (LegendRequest legend : layers) { FeatureType layer = legend.getFeatureType(); // style and rule to use for the current layer Style gt2Style = legend.getStyle(); if (gt2Style == null) { throw new NullPointerException("request.getStyle()"); } // get rule corresponding to the layer index // normalize to null for NO RULE String ruleName = legend.getRule(); // was null // width and height, we might have to rescale those in case of DPI usage int w = request.getWidth(); int h = request.getHeight(); // apply dpi rescale double dpi = RendererUtilities.getDpi(request.getLegendOptions()); double standardDpi = RendererUtilities.getDpi(Collections.emptyMap()); if (dpi != standardDpi) { double scaleFactor = dpi / standardDpi; w = (int) Math.round(w * scaleFactor); h = (int) Math.round(h * scaleFactor); DpiRescaleStyleVisitor dpiVisitor = new DpiRescaleStyleVisitor(scaleFactor); dpiVisitor.visit(gt2Style); gt2Style = (Style) dpiVisitor.getCopy(); } // apply UOM rescaling if we have a scale if (request.getScale() > 0) { double pixelsPerMeters = RendererUtilities.calculatePixelsPerMeterRatio( request.getScale(), request.getLegendOptions()); UomRescaleStyleVisitor rescaleVisitor = new UomRescaleStyleVisitor(pixelsPerMeters); rescaleVisitor.visit(gt2Style); gt2Style = (Style) rescaleVisitor.getCopy(); } boolean strict = request.isStrict(); final boolean transparent = request.isTransparent(); RenderedImage titleImage = null; // if we have more than one layer, we put a title on top of each layer legend if (layers.size() > 1 && !forceTitlesOff) { titleImage = getLayerTitle(legend, w, h, transparent, request); } // Check for rendering transformation boolean hasVectorTransformation = false; boolean hasRasterTransformation = false; List<FeatureTypeStyle> ftsList = gt2Style.featureTypeStyles(); for (int i = 0; i < ftsList.size(); i++) { FeatureTypeStyle fts = ftsList.get(i); Expression exp = fts.getTransformation(); if (exp != null) { ProcessFunction processFunction = (ProcessFunction) exp; Name processName = processFunction.getProcessName(); Map<String, Parameter<?>> outputs = Processors.getResultInfo(processName, null); if (outputs.isEmpty()) { continue; } Parameter<?> output = outputs.values().iterator().next(); // we assume there is only one output if (SimpleFeatureCollection.class.isAssignableFrom(output.getType())) { hasVectorTransformation = true; break; } else if (GridCoverage2D.class.isAssignableFrom(output.getType())) { hasRasterTransformation = true; break; } } } final boolean buildRasterLegend = (!strict && layer == null && LegendUtils.checkRasterSymbolizer(gt2Style)) || (LegendUtils.checkGridLayer(layer) && !hasVectorTransformation) || hasRasterTransformation; // Just checks LegendInfo currently, should check gtStyle final boolean useProvidedLegend = layer != null && legend.getLayerInfo() != null; RenderedImage legendImage = null; if (useProvidedLegend) { legendImage = getLayerLegend(legend, w, h, transparent, request); } if (buildRasterLegend) { final RasterLayerLegendHelper rasterLegendHelper = new RasterLayerLegendHelper(request, gt2Style, ruleName); final BufferedImage image = rasterLegendHelper.getLegend(); if (image != null) { if (titleImage != null) { layersImages.add(titleImage); } layersImages.add(image); } } else if (useProvidedLegend && legendImage != null) { if (titleImage != null) { layersImages.add(titleImage); } layersImages.add(legendImage); } else { final Feature sampleFeature; if (layer == null || hasVectorTransformation) { sampleFeature = createSampleFeature(); } else { sampleFeature = createSampleFeature(layer); } final FeatureTypeStyle[] ftStyles = gt2Style.featureTypeStyles().toArray(new FeatureTypeStyle[0]); final double scaleDenominator = request.getScale(); final Rule[] applicableRules; if (ruleName != null) { Rule rule = LegendUtils.getRule(ftStyles, ruleName); if (rule == null) { throw new ServiceException( "Specified style does not contains a rule named " + ruleName); } applicableRules = new Rule[] {rule}; } else { applicableRules = LegendUtils.getApplicableRules(ftStyles, scaleDenominator); } final NumberRange<Double> scaleRange = NumberRange.create(scaleDenominator, scaleDenominator); final int ruleCount = applicableRules.length; /** * A legend graphic is produced for each applicable rule. They're being held here until the * process is done and then painted on a "stack" like legend. */ final List<RenderedImage> legendsStack = new ArrayList<RenderedImage>(ruleCount); final SLDStyleFactory styleFactory = new SLDStyleFactory(); double minimumSymbolSize = MINIMUM_SYMBOL_SIZE; // get minSymbolSize from LEGEND_OPTIONS, if defined if (request.getLegendOptions().get("minSymbolSize") instanceof String) { String minSymbolSizeOpt = (String) request.getLegendOptions().get("minSymbolSize"); try { minimumSymbolSize = Double.parseDouble(minSymbolSizeOpt); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid minSymbolSize value: should be a number"); } } // calculate the symbols rescaling factor necessary for them to be // drawn inside the icon box double symbolScale = calcSymbolScale(w, h, layer, sampleFeature, applicableRules, minimumSymbolSize); for (int i = 0; i < ruleCount; i++) { final RenderedImage image = ImageUtils.createImage(w, h, (IndexColorModel) null, transparent); final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>(); final Graphics2D graphics = ImageUtils.prepareTransparency( transparent, LegendUtils.getBackgroundColor(request), image, hintsMap); graphics.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Feature sample = getSampleFeatureForRule(layer, sampleFeature, applicableRules[i]); FilterFactory ff = CommonFactoryFinder.getFilterFactory(); final Symbolizer[] symbolizers = applicableRules[i].getSymbolizers(); final GraphicLegend graphic = applicableRules[i].getLegend(); // If this rule has a legend graphic defined in the SLD, use it if (graphic != null) { if (this.samplePoint == null) { Coordinate coord = new Coordinate(w / 2, h / 2); try { this.samplePoint = new LiteShape2(geomFac.createPoint(coord), null, null, false); } catch (Exception e) { this.samplePoint = null; } } shapePainter.paint(graphics, this.samplePoint, graphic, scaleDenominator, false); } else { for (int sIdx = 0; sIdx < symbolizers.length; sIdx++) { Symbolizer symbolizer = symbolizers[sIdx]; if (symbolizer instanceof RasterSymbolizer) { // skip it } else { // rescale symbols if needed if (symbolScale > 1.0 && symbolizer instanceof PointSymbolizer) { PointSymbolizer pointSymbolizer = cloneSymbolizer(symbolizer); if (pointSymbolizer.getGraphic() != null) { double size = getPointSymbolizerSize(sample, pointSymbolizer, Math.min(w, h) - 4); pointSymbolizer .getGraphic() .setSize(ff.literal(size / symbolScale + minimumSymbolSize)); symbolizer = pointSymbolizer; } } Style2D style2d = styleFactory.createStyle(sample, symbolizer, scaleRange); LiteShape2 shape = getSampleShape(symbolizer, w, h); if (style2d != null) { shapePainter.paint(graphics, shape, style2d, scaleDenominator); } } } } if (image != null && titleImage != null) { layersImages.add(titleImage); titleImage = null; } legendsStack.add(image); graphics.dispose(); } // JD: changed legend behavior, see GEOS-812 // this.legendGraphic = scaleImage(mergeLegends(legendsStack), request); BufferedImage image = mergeLegends(legendsStack, applicableRules, request, forceLabelsOn, forceLabelsOff); if (image != null) { layersImages.add(image); } } } // all legend graphics are merged if we have a layer group BufferedImage finalLegend = mergeLegends(layersImages, null, request, forceLabelsOn, forceLabelsOff); if (finalLegend == null) { throw new IllegalArgumentException("no legend passed"); } return finalLegend; }