/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.wms.util; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.geoserver.config.GeoServer; import org.geoserver.ows.util.KvpUtils; import org.geoserver.platform.ServiceException; import org.geoserver.wms.MapLayerInfo; import org.geoserver.wms.WMS; import org.geotools.map.MapLayer; import org.geotools.styling.Style; import org.vfny.geoserver.util.Requests; import org.vfny.geoserver.wms.WMSMapContext; import org.vfny.geoserver.wms.requests.GetMapRequest; import org.vfny.geoserver.wms.requests.WMSRequest; import com.vividsolutions.jts.geom.Envelope; /** * Utility class for creating wms requests. * * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org * @see Requests */ public class WMSRequests { /** * Returns the base url for a wms request, something of the form: * <pre><protocol>://<server>:<port>/<context>/wms</pre> * * @param request The request. * @param geoServer GeoServer configuration. * * @return The base for wms requests. */ public static String getBaseUrl( HttpServletRequest request, WMS config ) { String baseUrl = Requests.getBaseUrl( request, config.getGeoServer() ); baseUrl = Requests.appendContextPath(baseUrl, "wms" ); return baseUrl; } /** * Returns the base url for a wms request, something of the form: * <pre><protocol>://<server>:<port>/<context>/wms</pre> * * @param req The wms request. * * @return The base for wms requests. */ public static String getBaseUrl( WMSRequest request ) { return getBaseUrl( request.getHttpServletRequest(), request.getWMS() ); } /** * Returns the base url for a wms request, something of the form: * <pre><protocol>://<server>:<port>/<context>/wms</pre> * * @param map A wms map context. * * @return The base for wms requests. */ public static String getBaseUrl( WMSMapContext map ) { return getBaseUrl( map.getRequest() ); } /** * Encodes the url of a GetMap request pointing to a tile cache if one * exists. * <p> * The tile cache location is determined from {@link GeoServer#getTileCache()}. * If the above method returns null this method falls back to the behaviour * of {@link #getGetMapUrl(WMSMapContext, MapLayer, Envelope, String[])}. * </p> * <p> * If the <tt>layer</tt> argument is <code>null</code>, the request is * made including all layers in the <tt>mapContexT</tt>. * </p> * <p> * If the <tt>bbox</tt> argument is <code>null</code>. {@link WMSMapContext#getAreaOfInterest()} * is used for the bbox parameter. * </p> * * @param req The getMap request. * @param layer The Map layer, may be <code>null</code>. * @param layerIndex The index of the layer in the request * @param bbox The bounding box of the request, may be <code>null</code>. * @param kvp Additional or overidding kvp parameters, may be <code>null</code> * * @return The full url for a getMap request. */ public static String getTiledGetMapUrl(GetMapRequest req, MapLayer layer, int layerIndex, Envelope bbox, String[] kvp) { String baseUrl = getTileCacheBaseUrl(req.getHttpServletRequest(), req.getWMS().getGeoServer()); if ( baseUrl == null ) { return getGetMapUrl( req, layer, layerIndex, bbox, kvp ); } return getGetMapUrl( baseUrl, req, layer.getTitle(), layerIndex, layer.getStyle().getName(), bbox, kvp ); } /** * Returns the full url to the tile cache used by GeoServer ( if any ). * <p> * If the tile cache set in the configuration ({@link GeoServer#getTileCache()}) * is set to an asbsolute url, it is simply returned. Otherwise the value * is appended to the scheme and host of the supplied <tt>request</tt>. * </p> * @param request The request. * @param geoServer The geoserver configuration. * * @return The url to the tile cache, or <code>null</code> if no tile * cache set. */ private static String getTileCacheBaseUrl(HttpServletRequest request, GeoServer geoServer) { //first check if tile cache set String tileCacheBaseUrl = (String) geoServer.getGlobal().getMetadata().get( "tileCache"); if (tileCacheBaseUrl != null) { //two possibilities, local path, or full remote path try { new URL(tileCacheBaseUrl); //full url, return it return tileCacheBaseUrl; } catch (MalformedURLException e1) { //try relative to the same host as request try { String url = Requests.appendContextPath(request.getScheme() + "://" + request.getServerName(), tileCacheBaseUrl); new URL(url); //cool return it return url; } catch (MalformedURLException e2) { //out of guesses } } } return null; } /** * Encodes the url of a GetMap request. * <p> * If the <tt>layer</tt> argument is <code>null</code>, the request is * made including all layers in the <tt>mapContexT</tt>. * </p> * <p> * If the <tt>bbox</tt> argument is <code>null</code>. {@link WMSMapContext#getAreaOfInterest()} * is used for the bbox parameter. * </p> * * @param req The getMap request * @param layer The Map layer, may be <code>null</code>. * @param layerIndex The index of the layer in the request * @param bbox The bounding box of the request, may be <code>null</code>. * @param kvp Additional or overidding kvp parameters, may be <code>null</code> * * @return The full url for a getMap request. */ public static String getGetMapUrl(GetMapRequest req, MapLayer layer, int layerIndex, Envelope bbox, String[] kvp) { //base url String baseUrl = getBaseUrl( req ); String layerName = layer != null ? layer.getTitle() : null; String style = layer != null ? layer.getStyle().getName() : null; return getGetMapUrl( baseUrl, req, layerName, layerIndex, style, bbox, kvp ); } /** * Encodes the url of a GetMap request. * <p> * If the <tt>layer</tt> argument is <code>null</code>, the request is * made including all layers in the <tt>mapContexT</tt>. * </p> * <p> * If the <tt>style</tt> argument is not <code>null</code> and the <tt>layer</tt> * argument is <code>null</code>, then the default style for that layer * is used. * </p> * <p> * If the <tt>bbox</tt> argument is <code>null</code>. {@link WMSMapContext#getAreaOfInterest()} * is used for the bbox parameter. * </p> * * @param req The getMap request * @param layer The layer name, may be <code>null</code>. * @param layerIndex The index of the layer in the request. * @param style The style name, may be <code>null</code> * @param bbox The bounding box of the request, may be <code>null</code>. * @param kvp Additional or overidding kvp parameters, may be <code>null</code> * * @return The full url for a getMap request. */ public static String getGetMapUrl(GetMapRequest req, String layer, int layerIndex, String style, Envelope bbox, String[] kvp) { //base url String baseUrl = getBaseUrl( req ); return getGetMapUrl( baseUrl, req, layer, layerIndex, style, bbox, kvp ); } /** * Encodes the url of a GetLegendGraphic request. * * @param req The wms request. * @param layer The Map layer, may not be <code>null</code>. * @param kvp Additional or overidding kvp parameters, may be <code>null</code> * * @return The full url for a getMap request. */ public static String getGetLegendGraphicUrl( WMSRequest req, MapLayer layer, String[] kvp ) { //parameters HashMap params = new HashMap(); params.put("service", "wms"); params.put("request", "GetLegendGraphic"); params.put("version", "1.1.1"); params.put("format", "image/png"); params.put("layer", layer.getTitle()); params.put("style", layer.getStyle().getName()); params.put("height", "20"); params.put("width", "20"); //overrides / additions for (int i = 0; (kvp != null) && (i < kvp.length); i += 2) { params.put(kvp[i], kvp[i + 1]); } return encode(getBaseUrl(req),params); } /** * Helper method for encoding GetMap request. * */ static String getGetMapUrl( String baseUrl, GetMapRequest req, String layer, int layerIndex, String style, Envelope bbox, String[] kvp ) { //parameters HashMap params = new HashMap(); params.put("service", "wms"); params.put("request", "GetMap"); params.put("version", "1.1.1"); params.put( "format", req.getFormat() ); StringBuffer layers = new StringBuffer(); StringBuffer styles = new StringBuffer(); boolean useLayerIndex = true; int count=0; for ( int i = 0; i < req.getLayers().length; i++ ) { if (layer != null && layer.equals( req.getLayers()[i].getName() ) ) { ++count; } } // only one of each layer in the request if (count == 1) { useLayerIndex = false; } if ( layer != null ) { layers.append( layer ); if ( style != null ) { styles.append( style ); } else { //use default for layer if (useLayerIndex) { styles.append( req.getLayers()[layerIndex].getDefaultStyle().getName() ); } else { for ( int i = 0; i < req.getLayers().length; i++ ) { if ( layer.equals( req.getLayers()[i].getName() ) ) { styles.append( req.getLayers()[i].getDefaultStyle().getName() ); } } } } } else { //no layer specified, use layers+styles specified by request for ( int i = 0; i < req.getLayers().length; i++ ) { MapLayerInfo mapLayer = req.getLayers()[ i ]; Style s = (Style) req.getStyles().get( 0 ); layers.append( mapLayer.getName() ).append( "," ); styles.append( s.getName() ).append( "," ); } layers.setLength(layers.length() - 1); styles.setLength(styles.length() - 1); } params.put("layers", layers.toString()); params.put("styles", styles.toString()); //filters, we grab them from the original raw kvp since re-encoding // them from objects is kind of silly if (layer != null) { //only get filters for the layer int index = 0; if (useLayerIndex) { index = layerIndex; }else{ for ( ; index < req.getLayers().length; index++) { if ( req.getLayers()[index].getName().equals( layer ) ) { break; } } } if ( req.getRawKvp().get("filter") != null ) { //split out the filter we need List filters = KvpUtils.readFlat((String) req.getRawKvp().get("filter"), KvpUtils.OUTER_DELIMETER); params.put( "filter", filters.get(index) ); } else if ( req.getRawKvp().get("cql_filter") != null ) { //split out the filter we need List filters = KvpUtils.readFlat((String) req.getRawKvp().get("cql_filter"), KvpUtils.CQL_DELIMITER); params.put( "cql_filter", filters.get(index) ); } else if ( req.getRawKvp().get("featureid") != null ) { //semantics of feautre id slightly different, replicate entire value params.put("featureid", req.getRawKvp().get("featureid")); } } else { //include all if ( req.getRawKvp().get("filter") != null ) { params.put("filter",req.getRawKvp().get("filter") ); } else if ( req.getRawKvp().get("cql_filter") != null ) { params.put("cql_filter",req.getRawKvp().get("cql_filter") ); } else if (req.getRawKvp().get("featureid") != null ) { params.put("featureid", req.getRawKvp().get("featureid")); } } //image params params.put("height", String.valueOf(req.getHeight())); params.put("width", String.valueOf(req.getWidth())); params.put( "transparent", ""+req.isTransparent() ); //bbox if (bbox == null) { bbox = req.getBbox(); } if (bbox != null) { params.put("bbox", encode(bbox)); } //srs params.put( "srs", req.getSRS() ); //format options if ( req.getFormatOptions() != null && !req.getFormatOptions().isEmpty()) { params.put( "format_options", encodeFormatOptions(req.getFormatOptions())); } //overrides / additions for (int i = 0; (kvp != null) && (i < kvp.length); i += 2) { params.put(kvp[i], kvp[i + 1]); } return encode(baseUrl, params); } /** * Encodes a map of formation options to be used as the value in a kvp. * * @param formatOptions The map of formation options. * * @return A string of the form 'key1:value1,value2;key2:value1;...', or the empty * string if the formatOptions map is empty. * */ public static String encodeFormatOptions( Map formatOptions ) { if ( formatOptions == null || formatOptions.isEmpty() ) { return ""; } StringBuffer sb = new StringBuffer(); for ( Iterator e = formatOptions.entrySet().iterator(); e.hasNext(); ) { Map.Entry entry = (Map.Entry) e.next(); String key = (String) entry.getKey(); Object val = entry.getValue(); sb.append( key ).append( ":" ); if ( val instanceof Collection ) { Iterator i = ((Collection)val).iterator(); while( i.hasNext() ) { sb.append( i.next() ).append( "," ); } sb.setLength(sb.length()-1); } else if ( val.getClass().isArray() ) { int len = Array.getLength( val ); for ( int i = 0; i < len; i++ ) { Object o = Array.get( val, i ); if ( o != null ) { sb.append( o ).append( "," ); } } sb.setLength(sb.length()-1); } else { sb.append( val.toString() ); } sb.append( ";" ); } sb.setLength( sb.length() ); return sb.toString(); } /** * Helper method to encode an envelope to be used in a wms request. */ static String encode(Envelope box) { return new StringBuffer().append(box.getMinX()).append(",").append(box.getMinY()).append(",") .append(box.getMaxX()).append(",").append(box.getMaxY()).toString(); } /** * Helper method for encoding a baseurl and some kvp params into a single * url. * * @param baseUrl The base url of the encoded request. * @param kvp The key value pairts of the request. * * @return The full request url. */ static String encode(String baseUrl, Map kvp) { StringBuffer query = new StringBuffer(); for (Iterator e = kvp.entrySet().iterator(); e.hasNext();) { Map.Entry entry = (Map.Entry) e.next(); String name = (String)entry.getKey(); String value = (String)entry.getValue(); try { name = URLEncoder.encode(name, "UTF-8"); if(value == null){ value = ""; }else{ value = URLEncoder.encode(value, "UTF-8"); } } catch (UnsupportedEncodingException uex) { throw new ServiceException("Error encoding name-value pairs" + uex.getMessage(), uex); } query.append(name).append("=").append(value).append("&"); } query.setLength(query.length() - 1); return Requests.appendQueryString( baseUrl, query.toString() ); } }