/* (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; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.LegendInfo; import org.geoserver.platform.ServiceException; import org.geotools.feature.NameImpl; import org.geotools.styling.Style; import org.geotools.util.Converters; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; /** * Holds the parsed parameters for a GetLegendGraphic WMS request. * * <p> * The GET parameters of the GetLegendGraphic operation are defined as follows (from SLD 1.0 spec, ch.12):<br> * * <pre> * <table> * <tr><td><b>Parameter</b></td><td><b>Required</b></td><td><b>Description</b></td></tr> * <tr><td>VERSION </td><td>Required </td><td>Version as required by OGC interfaces.</td></tr> * <tr><td>REQUEST </td><td>Required </td><td>Value must be GetLegendRequest . </td></tr> * <tr><td>LAYER </td><td>Required </td><td>Layer for which to produce legend graphic. A layergroup can be specified, too. In this case, STYLE and RULE parameters can have multiple values (separated by commas), one for each of the group layers.</td></tr> * <tr><td>STYLE </td><td>Optional </td><td>Style of layer for which to produce legend graphic. If not present, the default style is selected. The style may be any valid style available for a layer, including non-SLD internally-defined styles. A list of styles separated by commas can be used to specify styles for single layers of a layergroup. To override default style only for some layers leave empty the not overridden ones in the list (ex. style1,,style3,style4 to use default style for layer 2).</td></tr> * <tr><td>FEATURETYPE </td><td>Optional </td><td>Feature type for which to produce the legend graphic. This is not needed if the layer has only a single feature type. </td></tr> * <tr><td>RULE </td><td>Optional </td><td>Rule of style to produce legend graphic for, if applicable. In the case that a style has multiple rules but no specific rule is selected, then the map server is obligated to produce a graphic that is representative of all of the rules of the style. A list of rules separated by commas can be used to specify rules for single layers of a layergroup. To specify rule only for some layers leave empty the not overridden ones in the list (ex. rule1,,rule3,rule4 to not specify rule for layer 2).</td></tr> * <tr><td>SCALE </td><td>Optional </td><td>In the case that a RULE is not specified for a style, this parameter may assist the server in selecting a more appropriate representative graphic by eliminating internal rules that are outof- scope. This value is a standardized scale denominator, defined in Section 10.2</td></tr> * <tr><td>SLD </td><td>Optional </td><td>This parameter specifies a reference to an external SLD document. It works in the same way as the SLD= parameter of the WMS GetMap operation. </td></tr> * <tr><td>SLD_BODY </td><td>Optional </td><td>This parameter allows an SLD document to be included directly in an HTTP-GET request. It works in the same way as the SLD_BODY= parameter of the WMS GetMap operation.</td></tr> * <tr><td>FORMAT </td><td>Required </td><td>This gives the MIME type of the file format in which to return the legend graphic. Allowed values are the same as for the FORMAT= parameter of the WMS GetMap request. </td></tr> * <tr><td>WIDTH </td><td>Optional </td><td>This gives a hint for the width of the returned graphic in pixels. Vector-graphics can use this value as a hint for the level of detail to include. </td></tr> * <tr><td>HEIGHT </td><td>Optional </td><td>This gives a hint for the height of the returned graphic in pixels. </td></tr> * <tr><td>LANGUAGE </td><td>Optional </td><td>Permits to have internationalized text in legend output. </td></tr> * <tr><td>EXCEPTIONS </td><td>Optional </td><td>This gives the MIME type of the format in which to return exceptions. Allowed values are the same as for the EXCEPTIONS= parameter of the WMS GetMap request.</td></tr> * <tr><td>TRANSPARENT </td><td>Optional </td><td><code>true</code> if the legend image background should be transparent. Defaults to <code>false</code>.</td></tr> * </table> * </pre> * * * </p> * <p> * There's also a custom {@code STRICT} parameter that defaults to {@code true} and controls whether the mandatory parameters are to be checked. This * is useful mainly to be able of requesting a legend graphic for no layer in particular, so the LAYER parameter can be omitted. * </p> * <p> * The GetLegendGraphic operation itself is optional for an SLD-enabled WMS. It provides a general mechanism for acquiring legend symbols, beyond the * LegendURL reference of WMS Capabilities. Servers supporting the GetLegendGraphic call might code LegendURL references as GetLegendGraphic for * interface consistency. Vendorspecific parameters may be added to GetLegendGraphic requests and all of the usual OGC-interface options and rules * apply. No XML-POST method for GetLegendGraphic is presently defined. * </p> * <p> * In addition to the official parameters {@link #getLegendOptions()} is used to refining the appearance of of the generated legend. * </p> * <p> * Finally as a data structure {@link GetLegendGraphic} is used to collect additional context. Rendering environment {@link #getEnv()} and * {@link #locale}. LayerInfo configuration settings are available using methods like {@link #getTitle(Name)}. * </p> * * @author Gabriel Roldan * @version $Id$ */ public class GetLegendGraphicRequest extends WMSRequest { /** * Legend option to enable feature count matching */ public static final String COUNT_MATCHED_KEY = "countMatched"; /** * Details collected for an individual LegendGraphic including * layer, title, style and optional legend graphic. * <p> * This information is parsed from the GetLegendGraphicRequest and supplemented * with layer and style configuration as required. This information is provided * as a data structure in order to avoid duplicating logic in GetLegendGraphicKvpReader * and BufferedImageLegendGraphicBuild. * <p> * LegendRequest acts as simple data object with equality derived from layer name (enought to allow it to behave well in a List). * Note that LegendRequest is specific to a single {@link GetLegendGraphicRequest} and should not be cached. It represents * the state of the system at the time of parsing. * * @author Jody Garnett (Boundless) */ public class LegendRequest { private String layer; private Name layerName; private FeatureType featureType; private String styleName; private String title; /** Optional rule used to refine presentation of style */ private String rule; /** Style deterimed from a review of request parameters */ private Style style; /** Optional legend info (from layer info or style info) */ private LegendInfo legendInfo; /** Optional layer info (if available) */ private LayerInfo layerInfo; /** Optional layer group info (if available ) */ private LayerGroupInfo layerGroupInfo; /** * LegendRequest for a style, no associated featureType. */ public LegendRequest(){ this.layer = ""; this.featureType = null; this.layerName = new NameImpl(""); } /** * LegendRequest for a feature type, additional details (title and legend graphic) provided by MapLayerInfo. * * @param featureType */ public LegendRequest(FeatureType featureType ){ if( featureType == null ){ throw new NullPointerException("FeatureType required for LegendRequest"); } this.featureType = featureType; this.layerName = featureType.getName(); } public String getLayer() { return layer; } public void setLayer(String layerName) { this.layer = layerName; } public Name getLayerName(){ return layerName; } public FeatureType getFeatureType() { return featureType; } public void setFeatureType(FeatureType featureType) { this.featureType = featureType; } public LayerGroupInfo getLayerGroupInfo() { return layerGroupInfo; } public void setLayerGroupInfo(LayerGroupInfo layerGroupInfo) { this.layerGroupInfo = layerGroupInfo; } public LayerInfo getLayerInfo() { return layerInfo; } public void setLayerInfo(LayerInfo layerInfo) { this.layerInfo = layerInfo; } /** * Optional rule name used when rendering this legend. * * @return rule name, or null if empty */ public String getRule() { if("".equals(rule)) { return null; } return rule; } public void setRule(String rule) { this.rule = rule; } public String getStyleName() { return styleName; } public void setStyleName(String styleName) { this.styleName = styleName; } /** * Provided layer title, or layer name if not provided. * * We choose a title with the following priority: * <ul> * <li>Layer Title</li> * <li>Layer Name - often obtained from native resource name</li> * </ul> * @return layer title if provided, or layer name as a default */ public String getTitle() { if (title == null || "".equals(title)) { title=getLayerName().getLocalPart(); } return title; } /** * Used to provide a legend title (from MapLayerInfo). * <p> * If the title is empty or null the layer name will be used. * * @param title */ public void setTitle(String title) { this.title = title; } /** * The Style object(s) for styling the legend graphic, or layer's default if not provided. This * style can be acquired by evaluating the STYLE parameter, which provides one of the layer's * named styles, the SLD parameter, which provides a URL for an external SLD document, or the * SLD_BODY parameter, which provides the SLD body in the request body. */ public Style getStyle() { return style; } public void setStyle(Style style) { this.style = style; } public LegendInfo getLegendInfo() { return legendInfo; } public void setLegendInfo(LegendInfo legendInfo) { this.legendInfo = legendInfo; } @Override public String toString() { return "LegendRequest [layer=" + layer + ", name="+layerName+" styleName=" + styleName + ", title=" + title + ", legendInfo=" + legendInfo + "]"; } } public static final String SLD_VERSION = "1.0.0"; /** * default legend graphic width, in pixels, to apply if no WIDTH parameter was passed */ public static final int DEFAULT_WIDTH = 20; /** * default legend graphic height, in pixels, to apply if no WIDTH parameter was passed */ public static final int DEFAULT_HEIGHT = 20; /** * The default image format in which to produce a legend graphic. Not really used when * performing user requests, since FORMAT is a mandatory parameter, but by now serves as a * default for expressing LegendURL layer attribute in GetCapabilities. */ public static final String DEFAULT_FORMAT = "image/png"; /** The featuretype(s) of the requested LAYER(s) */ private List<LegendRequest> legends=new ArrayList<LegendRequest>(); /** * should hold FEATURETYPE parameter value, though not used by now, since GeoServer WMS still * does not supports nested layers and layers has only a single feature type. This should change * in the future. */ private String featureType; /** * holds the standarized scale denominator passed as the SCALE parameter value, or * <code>-1.0</code> if not provided */ private double scale = -1d; /** * the mime type of the file format in which to return the legend graphic, as requested by the * FORMAT request parameter value. */ private String format; /** * the width in pixels of the returned graphic, or <code>DEFAULT_WIDTH</code> if not provided */ private int width = DEFAULT_WIDTH; /** * the height in pixels of the returned graphic, or <code>DEFAULT_HEIGHT</code> if not provided */ private int height = DEFAULT_HEIGHT; /** mime type of the format in which to return exceptions information. */ private String exceptionsFormat = GetMapRequest.SE_XML; /** * holds the geoserver-specific getLegendGraphic options for controlling things like the label * font, label font style, label font antialiasing, etc. */ private Map<String,Object> legendOptions; /** * Whether the legend graphic background shall be transparent or not. */ private boolean transparent; private boolean strict = true; /** * Optional locale to be used for text in output. * */ private Locale locale; /** * Contains the parsed kvp items */ private Map<String, Object> kvp; private WMS wms; /** * Creates a new GetLegendGraphicRequest object. * * @param wms * The WMS configuration object. */ public GetLegendGraphicRequest() { super("GetLegendGraphic"); } public String getExceptions() { return exceptionsFormat; } public void setExceptions(String exceptionsFormat) { this.exceptionsFormat = exceptionsFormat; } public String getFeatureType() { return featureType; } public void setFeatureType(String featureType) { this.featureType = featureType; } public String getFormat() { return format; } public void setFormat(String format) { this.format = format; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } /** * Legend details in order requested. * * @return legend in order requested */ public List<LegendRequest> getLegends() { return legends; } /** * List of layer FeatureType in order requested. * @return layer FeatureType in order requested * @deprecated Use {@link #getLegends()} */ public List<FeatureType> getLayers() { List<FeatureType> types = new ArrayList<FeatureType>( legends.size()); for( LegendRequest layer : legends ){ types.add( layer.getFeatureType() ); } return Collections.unmodifiableList(types); } /** * Initialize {@link GetLegendGraphicRequest} with list of layers to draw. * * @param layers * @deprecated Use {@link #getLegends()} */ public void setLayers(List<FeatureType> layers) { List<LegendRequest> list = new ArrayList<LegendRequest>(layers.size()); for( FeatureType type : layers ){ LegendRequest legendRequest = new LegendRequest( type ); list.add(legendRequest ); } this.legends = list; } /** * Set optional layer title (from MapLayerInfo). * <p> * Note {@link #legends} entry must all ready be added. * * @param featureTypeName * @param title Layer title from MapLayerInfo * @deprecated Use getLegendRequest(name).setTitle(title); */ public void setTitle(Name featureTypeName,String title) { getLegend(featureTypeName).setTitle(title); } /** * Layer title. * @param featureTypeName * @return Title of layer (if provided) * @deprecated Use getLegendRequest(name).getTitle(); */ public String getTitle(Name featureTypeName) { return getLegend(featureTypeName).getTitle(); } /** * Lookup LegendRequest by native FeatureType name. * * @param featureTypeName * @return Matching LegendRequest */ public LegendRequest getLegend(Name featureTypeName) { for( LegendRequest legend : legends ){ if( featureTypeName.equals( legend.getLayerName() ) ){ return legend; } } return null; // not found! } /** * Used to clear {@link #legends} and configure with a feature type. * @param layer */ public void setLayer(FeatureType layer) { this.legends.clear(); if(layer==null) { this.legends.add(new LegendRequest()); } else { this.legends.add(new LegendRequest(layer)); } } /** * Access to rules in the same order as {@link #legends}. * * @return rules in the same order as layers * @deprecated Use getLegendRequest(name).getRule() */ public List<String> getRules() { List<String> rules = new ArrayList<String>( legends.size()); for( LegendRequest layer : legends ){ rules.add( layer.getRule() ); } return Collections.unmodifiableList(rules); } /** * Set rules in the same order as {@link #legends}. * * @param rules rules in the same order as layers * @deprecated Use getLegendRequest(name).setRule(rule) */ public void setRules(List<String> rules) { Iterator<String> s = rules.iterator(); for( LegendRequest legend : legends ){ if( !s.hasNext() ){ break; // no more styles } String rule = s.next(); legend.setRule( rule ); } } /** * Shortcut used to set the rule for the first layer. * @param rule */ public void setRule(String rule) { // Will set rule for first LegendRequest setRules( Collections.singletonList(rule)); } public double getScale() { return scale; } public void setScale(double scale) { this.scale = scale; } /** * Access to styles in the same order as {@link #legends}. * * @return stules in the same order as layers * @deprecated Use getLegendRequest(name).getStyle() */ public List<Style> getStyles() { List<Style> styles = new ArrayList<Style>( legends.size()); for( LegendRequest layer : legends ){ styles.add( layer.getStyle() ); } return Collections.unmodifiableList(styles); } /** * Assign resolved style information to {@link #legends}. * <p> * Styles must be provided in the same order as the layers list. * * @param styles * @deprecated Use getLegendGraphic(name).setStyle(style) */ public void setStyles(List<Style> styles) { Iterator<Style> s = styles.iterator(); for( LegendRequest legend : legends ){ if( !s.hasNext() ){ break; // no more styles } Style style = s.next(); legend.setStyle( style ); } } /** * Shortcut used to set the style for the first layer. * @param style */ public void setStyle(Style style) { // this will set only the first LegendRequest setStyles( Collections.singletonList(style)); } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } /** * Returns the possibly empty set of key/value pair parameters to control some aspects of legend * generation. * <p> * These parameters are meant to be passed as the request parameter * <code>"LEGEND_OPTIONS"</code> with the format * <code>LEGEND_OPTIONS=multiKey:val1,val2,val3;singleKey:val</code>. * </p> * <p> * The known options, all optional, are: * <ul> * <li><code>fontName</code>: name of the system font used for legend rule names. Defaults to * "Sans-Serif" * <li><code>fontStyle</code>: one of "plain", "italic" or "bold" * <li><code>fontSize</code>: integer for the font size in pixels * <li><code>fontColor</code>: a <code>String</code> that represents an opaque color as a 24-bit * integer * <li><code>bgColor</code>: allows to override the legend background color * <li><code>fontAntiAliasing</code>: a boolean indicating whether to use antia aliasing in font * rendering. Anything of the following works: "yes", "true", "1". Anything else means false. * <li><code>forceLabels</code>: "on" means labels will always be drawn, even if only one rule * is available. "off" means labels will never be drawn, even if multiple rules are available. * <li><code>forceTitles</code>: "off" means titles will never be drawn, even if multiple layers * are available. * <li><code>minSymbolSize</code>: a number defining the minimum size to be rendered for a * symbol (defaults to 3). * * </ul> * </p> * * @return Map<String,Object> */ @SuppressWarnings("unchecked") public Map<String,Object> getLegendOptions() { return (Map<String, Object>) (legendOptions == null ? Collections.emptyMap() : legendOptions); } /** * Sets the legend options parameters. * * @param legendOptions * the key/value pair of legend options strings * @see #getLegendOptions() */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void setLegendOptions(Map legendOptions) { this.legendOptions = legendOptions; } /** * Sets the value of the background transparency flag depending on the value of the * <code>TRANSPARENT</code> request parameter. * * @param transparentBackground * whether the legend graphic background shall be transparent or not */ public void setTransparent(boolean transparentBackground) { this.transparent = transparentBackground; } /** * Returns the value of the optional request parameter <code>TRANSPARENT</code>, which might be * either the literal <code>true</code> or <code>false</code> and specifies if the background of * the legend graphic to return shall be transparent or not. * <p> * If the <code>TRANSPARENT</code> parameter is not specified, this property defaults to * <code>false</code>. * </p> * * @return whether the legend graphic background shall be transparent or not */ public boolean isTransparent() { return transparent; } /** * Returns the value for the legacy {@code STRICT} parameter that controls whether LAYER is * actually required (if not, STYLE shall be provided) * * @return {@code true} by default, the value set thru {@link #setStrict(boolean)} otherwise */ public boolean isStrict() { return strict; } public void setStrict(boolean strict) { this.strict = strict; } /** SLD replacement */ private Map<String, Object> env = new HashMap<String, Object>(); /** * Map of strings that make up the SLD enviroment for variable substitution * * @return Map<String,Object> */ public Map<String,Object> getEnv() { return env; } /** * Sets the SLD environment substitution * * @param enviroment */ @SuppressWarnings({ "rawtypes", "unchecked" }) public void setEnv(Map enviroment) { this.env = enviroment; } /** * Sets the optional Locale to be used for text in legend output. * * @param locale */ public void setLocale(Locale locale) { this.locale = locale; } /** * Gets the locale to be used for text in output * (null to use default locale). * */ public Locale getLocale() { return this.locale; } /** * Parses and returns a legend option to a given type, if a value is present but cannot be * converted to the target type an exception will be thrown */ public <T> T getLegendOption(String key, Class<T> optionClass) { if(legendOptions == null) { return null; } Object value = legendOptions.get(key); if(value == null) { return null; } T converted = Converters.convert(value, optionClass); if(converted == null) { throw new ServiceException("Invalid syntax for option " + key + ", cannot be convered to a " + optionClass.getSimpleName()); } return converted; } /** * The parsed KVP map * @return */ public Map<String, Object> getKvp() { return kvp; } /** * Sets the parsed KVP map * @param kvp */ public void setKvp(Map<String, Object> kvp) { this.kvp = kvp; } public WMS getWms() { return wms; } public void setWms(WMS wms) { this.wms = wms; } }