/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.ogcwebservices.wms.operation; import java.awt.Point; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.ColorUtils; import org.deegree.framework.util.StringTools; import org.deegree.graphics.sld.StyledLayerDescriptor; import org.deegree.ogcbase.ExceptionCode; import org.deegree.ogcwebservices.InconsistentRequestException; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.wms.InvalidPointException; /** * @author Katharina Lupp <a href="mailto:k.lupp@web.de">Katharina Lupp </a> * @version $Revision: 1.18 $ $Date: 2006/11/22 15:38:31 $ */ public class GetFeatureInfo extends WMSRequestBase { private static final long serialVersionUID = 1197866346790857492L; private static final ILogger LOGGER = LoggerFactory.getLogger( GetFeatureInfo.class ); private List<String> queryLayers = null; private Point clickPoint = null; private String exceptions = null; private String infoFormat = null; private StyledLayerDescriptor sld = null; private GetMap getMapRequestCopy = null; private int featureCount = 1; private boolean infoFormatIsDefault = false; /** * creates a <tt>WMSFeatureInfoRequest</tt> from the request parameters. * * @return an instance of <tt>WMSFeatureInfoRequest</tt> * @param version * VERSION=version (R): Request version. * @param id the request id * @param queryLayers * QUERY_LAYERS=layer_list (R): Comma-separated list of one or * more layers to be queried. * @param getMapRequestCopy * <map_request_copy> (R): Partial copy of the Map request * parameters that generated the map for which information is * desired. * @param infoFormat * INFO_FORMAT=output_format (O): Return format of feature * information (MIME type). * @param featureCount * FEATURE_COUNT=number (O): Number of features about which to * return information (default=1). * @param clickPoint * X=pixel_column (R): X coordinate in pixels of feature * (measured from upper left corner=0) Y=pixel_row (R): Y * coordinate in pixels of feature (measured from upper left * corner=0) * @param exceptions * EXCEPTIONS=exception_format (O): The format in which * exceptions are to be reported by the WMS * (default=application/vnd.ogc.se_xml). * @param sld * StyledLayerDescriptor * @param vendorSpecificParameter * Vendor-specific parameters (O): Optional experimental * parameters. */ public static GetFeatureInfo create( String version, String id, String[] queryLayers, GetMap getMapRequestCopy, String infoFormat, int featureCount, java.awt.Point clickPoint, String exceptions, StyledLayerDescriptor sld, Map<String, String> vendorSpecificParameter ) { LOGGER.entering(); GetFeatureInfo fir = new GetFeatureInfo( version, id, queryLayers, getMapRequestCopy, infoFormat, featureCount, clickPoint, exceptions, sld, vendorSpecificParameter ); LOGGER.exiting(); return fir; } /** * creates a <tt>WMSFeatureInfoRequest</tt> from a <tt>HashMap</tt> that * contains the request parameters as key-value-pairs. Keys are expected to * be in upper case notation. * * @param model * <tt>HashMap</tt> containing the request parameters * @return an instance of <tt>WMSFeatureInfoRequest</tt> * @throws OGCWebServiceException */ public static GetFeatureInfo create( Map<String, String> model ) throws OGCWebServiceException { LOGGER.entering(); // VERSION String version = model.get( "VERSION" ); if ( version == null ) { version = model.get( "WMTVER" ); } if ( version == null ) { throw new InconsistentRequestException( "VERSION-value must be set in the GetFeatureInfo request" ); } // Some WMS Client, like ArcMAP, only send the QUERY_LAYERS parameter without a LAYERS parameter. // However, some WMS server like MapServer fail in case there is no LAYERS parameter send. // Thus, we add a LAYERS parameter in case only a QUERY_LAYERS parameter is available. // -> Keep this in sync with the code in com.camptocamp.owsproxy.OWSProxyServlet::doGet if (model.containsKey("QUERY_LAYERS") && !model.containsKey("LAYERS")) { model.put("LAYERS", model.get("QUERY_LAYERS")); } boolean is130 = ( "1.3.0".compareTo( version ) <= 0 ); // ID String id = model.get( "ID" ); if ( id == null ) { throw new InconsistentRequestException( "ID-value must be set in the GetFeatureInfo request" ); } // QUERY_LAYERS String layerlist = model.remove( "QUERY_LAYERS" ); String[] queryLayers = null; if ( layerlist != null ) { StringTokenizer st = new StringTokenizer( layerlist, "," ); queryLayers = new String[st.countTokens()]; int i = 0; while ( st.hasMoreTokens() ) { queryLayers[i++] = st.nextToken(); } } else { throw new InconsistentRequestException( "QUERY_LAYERS-value must be set in the GetFeatureInfo request" ); } // INFO_FORMAT (mime-type) String infoFormat = model.remove( "INFO_FORMAT" ); boolean infoFormatDefault = false; if ( infoFormat == null ) { infoFormat = "application/vnd.ogc.gml"; infoFormatDefault = true; } // FEATURE_COUNT (default=1) String feco = model.remove( "FEATURE_COUNT" ); int featureCount = 1; if ( feco != null ) { featureCount = Integer.parseInt( feco.trim() ); } if ( featureCount < 0 ) { featureCount = 1; } // X, Y (measured from upper left corner=0) String X; String Y; if ( is130 ) { X = "I"; Y = "J"; } else { X = "X"; Y = "Y"; } String xstring = model.remove( X ); String ystring = model.remove( Y ); java.awt.Point clickPoint = null; if ( ( xstring != null ) & ( ystring != null ) ) { try { int x = Integer.parseInt( xstring.trim() ); int y = Integer.parseInt( ystring.trim() ); clickPoint = new java.awt.Point( x, y ); } catch ( NumberFormatException nfe ) { LOGGER.logError( nfe.getLocalizedMessage(), nfe ); throw new OGCWebServiceException( "GetFeatureInfo", "Invalid point parameter", ExceptionCode.INVALID_POINT ); } } else { throw new InconsistentRequestException( X + "- and/or " + Y + "-value must be set in the GetFeatureInfo request" ); } // EXCEPTIONS (default=application/vnd.ogc.se_xml) String exceptions = model.get( "EXCEPTIONS" ); if ( exceptions == null ) { if ( is130 ) { exceptions = "XML"; } else { exceptions = "application/vnd.ogc.se_xml"; } } // <map_request_copy> GetMap getMapRequestCopy = null; try { if(!model.containsKey("FORMAT")){ model.put("FORMAT", "image/jpeg"); } getMapRequestCopy = GetMap.create( model ); } catch ( Exception ex ) { throw new InconsistentRequestException( "\nAn Exception " + "occured in creating the GetMap request-copy included in the " + "GetFeatureInfo-Operations:\n" + "--> Location: WMSProtocolFactory, createGetFeatureInfoRequest(int, HashMap)\n" + ex.getMessage() ); } // check for consistency if ( clickPoint.x > getMapRequestCopy.getWidth() || clickPoint.y > getMapRequestCopy.getHeight() ) { throw new InvalidPointException( "The requested point is not valid." ); } // VendorSpecificParameter; because all defined parameters has been // removed // from the model the vendorSpecificParameters are what left Map<String, String> vendorSpecificParameter = model; // StyledLayerDescriptor StyledLayerDescriptor sld = getMapRequestCopy.getStyledLayerDescriptor(); LOGGER.exiting(); GetFeatureInfo res = create( version, id, queryLayers, getMapRequestCopy, infoFormat, featureCount, clickPoint, exceptions, sld, vendorSpecificParameter ); res.infoFormatIsDefault = infoFormatDefault; return res; } /** * Creates a new WMSFeatureInfoRequest_Impl object. * * @param version * @param id * @param queryLayers * @param getMapRequestCopy * @param infoFormat * @param featureCount * @param clickPoint * @param exceptions * @param sld * @param vendorSpecificParameter */ private GetFeatureInfo( String version, String id, String[] queryLayers, GetMap getMapRequestCopy, String infoFormat, int featureCount, Point clickPoint, String exceptions, StyledLayerDescriptor sld, Map<String, String> vendorSpecificParameter ) { super( version, id, vendorSpecificParameter ); this.queryLayers = new ArrayList<String>(); setQueryLayers( queryLayers ); setGetMapRequestCopy( getMapRequestCopy ); setGetMapRequestCopy( getMapRequestCopy ); setFeatureCount( featureCount ); setClickPoint( clickPoint ); setExceptions( exceptions ); setStyledLayerDescriptor( sld ); setInfoFormat( infoFormat ); } /** * <map request copy> is not a name/value pair like the other parameters. * Instead, most of the GetMap request parameters that generated the * original map are repeated. Two are omitted because GetFeatureInfo * provides its own values: VERSION and REQUEST. The remainder of the GetMap * request shall be embedded contiguously in the GetFeatureInfo request. * @return a copy of the original request */ public GetMap getGetMapRequestCopy() { return getMapRequestCopy; } /** * sets the <GetMapRequestCopy> * @param getMapRequestCopy */ public void setGetMapRequestCopy( GetMap getMapRequestCopy ) { this.getMapRequestCopy = getMapRequestCopy; } /** * The required QUERY_LAYERS parameter states the map layer(s) from which * feature information is desired to be retrieved. Its value is a comma- * separated list of one or more map layers that are returned as an array. * This parameter shall contain at least one layer name, but may contain * fewer layers than the original GetMap request. * <p> * </p> * If any layer in this list is not contained in the Capabilities XML of the * WMS, the results are undefined and the WMS shall produce an exception * response. * @return the layer names */ public String[] getQueryLayers() { return queryLayers.toArray( new String[queryLayers.size()] ); } /** * adds the <QueryLayers> * @param queryLayers */ public void addQueryLayers( String queryLayers ) { this.queryLayers.add( queryLayers ); } /** * sets the <QueryLayers> * @param queryLayers */ public void setQueryLayers( String[] queryLayers ) { this.queryLayers.clear(); if ( queryLayers != null ) { for ( int i = 0; i < queryLayers.length; i++ ) { this.queryLayers.add( queryLayers[i] ); } } } /** * The optional INFO_FORMAT indicates what format to use when returning the * feature information. Supported values for a GetFeatureInfo request on a * WMS instance are listed as MIME types in one or more <Format>elements * inside the <Request><FeatureInfo>element of its Capabilities XML. The * entire MIME type string in <Format>is used as the value of the * INFO_FORMAT parameter. In an HTTP environment, the MIME type shall be set * on the returned object using the Content-type entity header. * <p> * </p> * <b>EXAMPLE: </b> <tt> The parameter INFO_FORMAT=application/vnd.ogc.gml * requests that the feature information be formatted in Geography Markup * Language (GML).</tt> * @return the format */ public String getInfoFormat() { return infoFormat; } /** * sets the <InfoFormat> * @param infoFormat */ public void setInfoFormat( String infoFormat ) { this.infoFormat = infoFormat; } /** * The optional FEATURE_COUNT parameter states the maximum number of * features for which feature information should be returned. Its value is a * positive integer greater than zero. The default value is 1 if this * parameter is omitted. * @return the count */ public int getFeatureCount() { return featureCount; } /** * sets the <FeatureCount> * @param featureCount */ public void setFeatureCount( int featureCount ) { this.featureCount = featureCount; } /** * The required X and Y parameters indicate a point of interest on the map. * X and Y identify a single point within the borders of the WIDTH and * HEIGHT parameters of the embedded GetMap request. The origin is set to * (0,0) centered in the pixel at the upper left corner; X increases to the * right and Y increases downward. X and Y are retruned as java.awt.Point * class/datastructure. * @return the point of interest */ public Point getClickPoint() { return clickPoint; } /** * sets the <ClickPoint> * @param clickPoint */ public void setClickPoint( Point clickPoint ) { this.clickPoint = clickPoint; } /** * The optional EXCEPTIONS parameter states the manner in which errors are * to be reported to the client. The default value is * application/vnd.ogc.se_xml if this parameter is absent from the request. * At present, not other values are defined for the WMS GetFeatureInfo * request. * @return the exception format */ public String getExceptions() { return exceptions; } /** * sets the <Exception> * @param exceptions */ public void setExceptions( String exceptions ) { this.exceptions = exceptions; } /** * returns the SLD the request is made of. This implies that a 'simple' HTTP * GET-Request will be transformed into a valid SLD. This is mandatory * within a JaGo WMS. * <p> * </p> * This mean even if a GetMap request is send using the HTTP GET method, an * implementing class has to map the request to a SLD data sructure. * @return the sld */ public StyledLayerDescriptor getStyledLayerDescriptor() { return sld; } /** * sets the SLD the request is made of. This implies that a 'simple' HTTP * GET-Request or a part of it will be transformed into a valid SLD. For * convenience it is asumed that the SLD names just a single layer to * generate display elements of. * @param sld */ public void setStyledLayerDescriptor( StyledLayerDescriptor sld ) { this.sld = sld; } @Override public String toString() { try { return getRequestParameter(); } catch ( OGCWebServiceException e ) { e.printStackTrace(); } return super.toString(); } /** * returns the parameter of a HTTP GET request. * */ @Override public String getRequestParameter() throws OGCWebServiceException { // indicates if the request parameters are decoded as SLD. deegree won't // perform SLD requests through HTTP GET if ( ( getMapRequestCopy.getBoundingBox() == null ) || ( queryLayers.size() == 0 ) ) { throw new OGCWebServiceException( "Operations can't be expressed as HTTP GET request " ); } StringBuffer sb = new StringBuffer( "service=WMS" ); if ( getVersion().compareTo( "1.0.0" ) <= 0 ) { sb.append( "&VERSION=" + getVersion() + "&REQUEST=feature_info" ); sb.append( "&TRANSPARENT=" + getMapRequestCopy.getTransparency() ); } else { sb.append( "&VERSION=" + getVersion() + "&REQUEST=GetFeatureInfo" ); sb.append( "&TRANSPARENCY=" + getMapRequestCopy.getTransparency() ); } sb.append( "&WIDTH=" + getMapRequestCopy.getWidth() ); sb.append( "&HEIGHT=" + getMapRequestCopy.getHeight() ); sb.append( "&FORMAT=" + getMapRequestCopy.getFormat() ); sb.append( "&EXCEPTIONS=" + getExceptions() ); sb.append( "&BGCOLOR=" ); sb.append( ColorUtils.toHexCode( "0x", getMapRequestCopy.getBGColor() ) ); if ( "1.3.0".compareTo( getVersion() ) <= 0 ) { sb.append( "&CRS=" + getMapRequestCopy.getSrs() ); sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getY() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getX() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() ); } else { sb.append( "&SRS=" + getMapRequestCopy.getSrs() ); sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getX() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getY() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() ); sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() ); } GetMap.Layer[] layers = getMapRequestCopy.getLayers(); String l = ""; String s = ""; for ( int i = 0; i < layers.length; i++ ) { l += ( layers[i].getName() + "," ); s += ( layers[i].getStyleName() + "," ); } l = l.substring( 0, l.length() - 1 ); s = s.substring( 0, s.length() - 1 ); sb.append( "&LAYERS=" + l ); // replace $DEFAULT with "", which is what WMSses expect StringTools.replace( s, "$DEFAULT", "", true ); sb.append( "&STYLES=" + s ); // TODO // append time, elevation and sample dimension String[] qlayers = getQueryLayers(); String ql = ""; for ( int i = 0; i < qlayers.length; i++ ) { ql += ( qlayers[i] + "," ); } ql = ql.substring( 0, ql.length() - 1 ); sb.append( "&QUERY_LAYERS=" + ql ); sb.append( "&FEATURE_COUNT=" + getFeatureCount() ); sb.append( "&INFO_FORMAT=" + getInfoFormat() ); if ( "1.3.0".compareTo( getVersion() ) <= 0 ) { sb.append( "&I=" + clickPoint.x ); sb.append( "&J=" + clickPoint.y ); } else { sb.append( "&X=" + clickPoint.x ); sb.append( "&Y=" + clickPoint.y ); } return sb.toString(); } /** * @return whether the info format is the default setting */ public boolean isInfoFormatDefault() { return infoFormatIsDefault; } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: GetFeatureInfo.java,v $ Revision 1.18 2006/11/22 15:38:31 schmitz Fixed more exception handling, especially for the GetFeatureInfo request. Revision 1.17 2006/10/17 20:31:18 poth *** empty log message *** Revision 1.16 2006/09/15 09:18:29 schmitz Updated WMS to use SLD or SLD_BODY sld documents as default when also giving LAYERS and STYLES parameters at the same time. Revision 1.15 2006/09/08 08:42:02 schmitz Updated the WMS to be 1.1.1 conformant once again. Cleaned up the WMS code. Added cite WMS test data. Revision 1.14 2006/09/05 08:33:23 schmitz GetFeatureInfo now uses one of the configured formats as default, not always GML. Revision 1.13 2006/09/01 12:28:43 schmitz Fixed two bugs: $DEFAULT is no longer appended instead of empty string in remote WMS requests (STYLE parameter). The FORMATS attribute now returns once more a String[] in the GetWMSLayerListener class. Revision 1.12 2006/07/13 12:24:45 poth adaptions required according to changes in org.deegree.ogcwebservice.wms.operations.GetMap Revision 1.11 2006/07/12 14:46:16 poth comment footer added ********************************************************************** */