/* (c) 2014 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.decoration;
import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.ows.AbstractDispatcherCallback;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.Request;
import org.geoserver.ows.util.CaseInsensitiveMap;
import org.geoserver.wms.GetLegendGraphicRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.legendgraphic.BufferedImageLegendGraphicBuilder;
import org.geoserver.wms.legendgraphic.LegendUtils;
import org.geoserver.wms.map.ImageUtils;
import org.geotools.map.Layer;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.styling.FeatureTypeStyle;
import org.opengis.style.Rule;
public class LegendDecoration extends AbstractDispatcherCallback implements MapDecoration {
private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.wms.responses");
private static int TITLE_INDENT = 5;
private static double BETWEEN_LEGENDS_PERCENT_INDENT = 0.05;
private final WMS wms;
private Map<String, String> options;
private ThreadLocal<List<LayerLegend>> legends = new ThreadLocal<List<LayerLegend>>();
private List<String> layers;
private boolean useSldTitle;
public LegendDecoration(WMS wms){
this.wms = wms;
this.layers = new ArrayList<String>();
}
@Override
public void finished(Request request) {
this.legends.remove();
}
public void loadOptions(Map<String, String> options) {
this.options = new HashMap(options);
String layers = this.options.remove("layers");
if (layers != null) {
String[] splittedLayers = layers.split(",");
this.layers.addAll(Arrays.asList(splittedLayers));
}
String sldTitle = this.options.remove("sldTitle");
if("true".equalsIgnoreCase(sldTitle) || "on".equalsIgnoreCase(sldTitle)) {
useSldTitle = true;
}
}
public Dimension findOptimalSize(Graphics2D g2d, WMSMapContent mapContext) {
double scaleDenominator = mapContext.getScaleDenominator(true);
double dpi = RendererUtilities.getDpi(mapContext.getRequest().getFormatOptions());
double standardDpi = RendererUtilities.getDpi(Collections.emptyMap());
double scaleFactor = dpi / standardDpi;
List<LayerLegend> layerLegends = new ArrayList<LayerLegend>();
for(Layer layer : mapContext.layers()) {
Rule[] applicableRules = LegendUtils.getApplicableRules(layer.getStyle().featureTypeStyles().toArray(new FeatureTypeStyle[0]), scaleDenominator);
if ((!layers.isEmpty() && !layers.contains(layer.getTitle())) || applicableRules.length == 0) {
continue;
}
BufferedImageLegendGraphicBuilder legendGraphicBuilder = new BufferedImageLegendGraphicBuilder();
GetLegendGraphicRequest request = new GetLegendGraphicRequest();
request.setLayer(layer.getFeatureSource().getSchema());
request.setTransparent(true);
request.setScale(scaleDenominator);
request.setStyle(layer.getStyle());
request.setWms(wms);
final Request dispatcherRequest = Dispatcher.REQUEST.get();
if(dispatcherRequest != null) {
request.setKvp(dispatcherRequest.getKvp());
request.setRawKvp(dispatcherRequest.getRawKvp());
}
Map legendOptions = new CaseInsensitiveMap(options);
legendOptions.putAll(mapContext.getRequest().getFormatOptions());
if(dispatcherRequest != null && dispatcherRequest.getKvp().get("legend_options") != null) {
legendOptions.putAll((Map) dispatcherRequest.getKvp().get("legend_options"));
}
request.setLegendOptions(legendOptions);
LayerLegend legend = new LayerLegend();
legend.request = request;
String title = findTitle(layer, wms.getGeoServer().getCatalog());
if (title != null) {
Font newFont = LegendUtils.getLabelFont(request);
newFont = newFont.deriveFont(Font.BOLD);
newFont = newFont.deriveFont((float) newFont.getSize() + 2);
Font oldFont = g2d.getFont();
g2d.setFont(newFont);
if (LegendUtils.isFontAntiAliasing(legend.request)) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
BufferedImage titleImage = LegendUtils.renderLabel(title, g2d, request);
g2d.setFont(oldFont);
legend.title = titleImage;
}
BufferedImage legendImage = legendGraphicBuilder.buildLegendGraphic(request);
legend.legend = legendImage;
layerLegends.add(legend);
}
legends.set(layerLegends);
int width = 0;
int height = 0;
for(LayerLegend legend : layerLegends) {
int legendHeight = legend.legend.getHeight();
int legendWidth = legend.legend.getWidth();
int titleWidth = 0;
int titleHeight = 0;
if (legend.title != null) {
titleWidth = legend.title.getWidth();
titleHeight = legend.title.getHeight();
}
int tmpHeight = legendHeight + titleHeight;
int tmpWidth = legendWidth;
if (titleWidth > legendWidth) {
tmpWidth = titleWidth;
tmpWidth += ((int) Math.ceil(TITLE_INDENT * 2 * scaleFactor)); // indent the title
}
Dimension size = new BasicStroke((float) scaleFactor).createStrokedShape(new Rectangle(0, 0, tmpWidth, tmpHeight)).getBounds().getSize();
height += (int) Math.ceil(size.getHeight() + (size.getHeight() * BETWEEN_LEGENDS_PERCENT_INDENT));
if (size.getWidth() > width) {
width = (int) Math.ceil(size.getWidth());
}
}
return new Dimension(width, height);
}
public void paint(Graphics2D g2d, Rectangle paintArea, WMSMapContent mapContext) throws Exception {
List<LayerLegend> legends = this.legends.get();
double dpi = RendererUtilities.getDpi(mapContext.getRequest().getFormatOptions());
double standardDpi = RendererUtilities.getDpi(Collections.emptyMap());
double scaleFactor = dpi / standardDpi;
Rectangle mainClip = g2d.getClipBounds();
int heightOffset = 0;
Iterator<LayerLegend> imageIterator = legends.iterator();
while(imageIterator.hasNext()) {
LayerLegend legend = imageIterator.next();
int height = legend.legend.getHeight();
int width = legend.legend.getWidth();
if (legend.title != null) {
height += legend.title.getHeight();
if (width < legend.title.getWidth()) {
width = legend.title.getWidth();
width += ((int) Math.ceil(TITLE_INDENT * 2 * scaleFactor)); // indent the title
}
}
Dimension size = new BasicStroke((float) scaleFactor).createStrokedShape(new Rectangle(0, 0, width, height)).getBounds().getSize();
int strokeHeight = (int) Math.ceil(size.getHeight());
int strokeWidth = (int) Math.ceil(size.getWidth());
// output image
BufferedImage finalLegend =
ImageUtils.createImage(
strokeWidth,
strokeHeight,
(IndexColorModel) null,
false
);
Graphics2D finalGraphics =
ImageUtils.prepareTransparency(
false,
LegendUtils.getBackgroundColor(legend.request),
finalLegend,
new HashMap<RenderingHints.Key, Object>()
);
// title
int titleHeightOffset = 0;
if (legend.title != null) {
finalGraphics.drawImage(
legend.title,
((strokeWidth - legend.title.getWidth()) / 2), // center
(strokeHeight - height),
null
);
titleHeightOffset += legend.title.getHeight();
}
// legend
finalGraphics.drawImage(
legend.legend,
(strokeWidth - width) / 2, // place on the left
//(strokeWidth - legend.legend.getWidth()) / 2, // center
(strokeHeight - height) + titleHeightOffset,
null
);
// border
finalGraphics.setColor(LegendUtils.DEFAULT_BORDER_COLOR);
finalGraphics.fill(
new BasicStroke((float) scaleFactor).createStrokedShape(
new Rectangle(
0,
0,
strokeWidth - 1,
strokeHeight - 1
)
)
);
// draw output image
g2d.drawImage(
finalLegend,
mainClip.x + (int) Math.ceil((paintArea.getWidth() - strokeWidth) / 2),
mainClip.y + heightOffset,
null
);
heightOffset += strokeHeight + (strokeHeight * BETWEEN_LEGENDS_PERCENT_INDENT);
}
}
private String findTitle(Layer layer, Catalog catalog) {
if (layer.getTitle() == null) {
return null;
}
String[] nameparts = layer.getTitle().split(":");
ResourceInfo resource = nameparts.length > 1 ? catalog.getResourceByName(nameparts[0],
nameparts[1], ResourceInfo.class) : catalog.getResourceByName(nameparts[0],
ResourceInfo.class);
if (useSldTitle && layer.getStyle() != null && layer.getStyle().getDescription()
!= null && layer.getStyle().getDescription().getTitle() != null) {
return layer.getStyle().getDescription().getTitle().toString();
} if (resource != null) {
return resource.getTitle();
} else {
return layer.getTitle();
}
}
private class LayerLegend {
public BufferedImage title;
public BufferedImage legend;
public GetLegendGraphicRequest request;
}
}