    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wms/operation/GetFeatureInfo.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005     Department of Geography, University of Bonn
    006     and
    007     lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035     ----------------------------------------------------------------------------*/
    036    package org.deegree.ogcwebservices.wms.operation;
    037
    038    import java.awt.Point;
    039    import java.util.ArrayList;
    040    import java.util.List;
    041    import java.util.Map;
    042    import java.util.StringTokenizer;
    043
    044    import org.deegree.framework.log.ILogger;
    045    import org.deegree.framework.log.LoggerFactory;
    046    import org.deegree.framework.util.ColorUtils;
    047    import org.deegree.framework.util.StringTools;
    048    import org.deegree.graphics.sld.StyledLayerDescriptor;
    049    import org.deegree.ogcbase.ExceptionCode;
    050    import org.deegree.ogcwebservices.InconsistentRequestException;
    051    import org.deegree.ogcwebservices.OGCWebServiceException;
    052    import org.deegree.ogcwebservices.wms.InvalidPointException;
    053
    054    /**
    055     * @author Katharina Lupp <a href="mailto:k.lupp@web.de">Katharina Lupp </a>
    056     * @version $Revision: 19752 $ $Date: 2009-09-24 12:04:55 +0200 (Do, 24. Sep 2009) $
    057     */
    058    public class GetFeatureInfo extends WMSRequestBase {
    059
    060        private static final long serialVersionUID = 1197866346790857492L;
    061
    062        private static final ILogger LOGGER = LoggerFactory.getLogger( GetFeatureInfo.class );
    063
    064        private List<String> queryLayers = null;
    065
    066        private Point clickPoint = null;
    067
    068        private String exceptions = null;
    069
    070        private String infoFormat = null;
    071
    072        private StyledLayerDescriptor sld = null;
    073
    074        private GetMap getMapRequestCopy = null;
    075
    076        private int featureCount = 1;
    077
    078        private boolean infoFormatIsDefault = false;
    079
    080        /**
    081         * creates a <tt>WMSFeatureInfoRequest</tt> from the request parameters.
    082         *
    083         * @return an instance of <tt>WMSFeatureInfoRequest</tt>
    084         * @param version
    085         *            VERSION=version (R): Request version.
    086         * @param id
    087         *            the request id
    088         * @param queryLayers
    089         *            QUERY_LAYERS=layer_list (R): Comma-separated list of one or more layers to be queried.
    090         * @param getMapRequestCopy
    091         *            &lt;map_request_copy&gt; (R): Partial copy of the Map request parameters that generated the map for
    092         *            which information is desired.
    093         * @param infoFormat
    094         *            INFO_FORMAT=output_format (O): Return format of feature information (MIME type).
    095         * @param featureCount
    096         *            FEATURE_COUNT=number (O): Number of features about which to return information (default=1).
    097         * @param clickPoint
    098         *            X=pixel_column (R): X coordinate in pixels of feature (measured from upper left corner=0) Y=pixel_row
    099         *            (R): Y coordinate in pixels of feature (measured from upper left corner=0)
    100         * @param exceptions
    101         *            EXCEPTIONS=exception_format (O): The format in which exceptions are to be reported by the WMS
    102         *            (default=application/vnd.ogc.se_xml).
    103         * @param sld
    104         *            StyledLayerDescriptor
    105         * @param vendorSpecificParameter
    106         *            Vendor-specific parameters (O): Optional experimental parameters.
    107         */
    108        public static GetFeatureInfo create( String version, String id, String[] queryLayers, GetMap getMapRequestCopy,
    109                                             String infoFormat, int featureCount, java.awt.Point clickPoint,
    110                                             String exceptions, StyledLayerDescriptor sld,
    111                                             Map<String, String> vendorSpecificParameter ) {
    112
    113            return new GetFeatureInfo( version, id, queryLayers, getMapRequestCopy, infoFormat, featureCount, clickPoint,
    114                                       exceptions, sld, vendorSpecificParameter );
    115
    116        }
    117
    118        /**
    119         * creates a <tt>WMSFeatureInfoRequest</tt> from a <tt>HashMap</tt> that contains the request parameters as
    120         * key-value-pairs. Keys are expected to be in upper case notation.
    121         *
    122         * @param model
    123         *            <tt>HashMap</tt> containing the request parameters
    124         * @return an instance of <tt>WMSFeatureInfoRequest</tt>
    125         * @throws OGCWebServiceException
    126         */
    127        public static GetFeatureInfo create( Map<String, String> model )
    128                                throws OGCWebServiceException {
    129
    130            // VERSION
    131            String version = model.get( "VERSION" );
    132            if ( version == null ) {
    133                version = model.get( "WMTVER" );
    134            }
    135            if ( version == null ) {
    136                throw new InconsistentRequestException( "VERSION-value must be set in the GetFeatureInfo request" );
    137            }
    138
    139            boolean is130 = ( "1.3.0".compareTo( version ) <= 0 );
    140
    141            // ID
    142            String id = model.get( "ID" );
    143            if ( id == null ) {
    144                throw new InconsistentRequestException( "ID-value must be set in the GetFeatureInfo request" );
    145            }
    146
    147            // QUERY_LAYERS
    148            String layerlist = model.remove( "QUERY_LAYERS" );
    149            String[] queryLayers = null;
    150
    151            if ( layerlist != null ) {
    152                StringTokenizer st = new StringTokenizer( layerlist, "," );
    153                queryLayers = new String[st.countTokens()];
    154                int i = 0;
    155                while ( st.hasMoreTokens() ) {
    156                    queryLayers[i++] = st.nextToken();
    157                }
    158            } else {
    159                throw new InconsistentRequestException( "QUERY_LAYERS-value must be set in the GetFeatureInfo request" );
    160            }
    161
    162            // INFO_FORMAT (mime-type)
    163            String infoFormat = model.remove( "INFO_FORMAT" );
    164            boolean infoFormatDefault = false;
    165            if ( infoFormat == null ) {
    166                infoFormat = "application/vnd.ogc.gml";
    167                infoFormatDefault = true;
    168            }
    169
    170            // FEATURE_COUNT (default=1)
    171            String feco = model.remove( "FEATURE_COUNT" );
    172            int featureCount = 1;
    173            if ( feco != null ) {
    174                featureCount = Integer.parseInt( feco.trim() );
    175            }
    176            if ( featureCount < 0 ) {
    177                featureCount = 1;
    178            }
    179
    180            // X, Y (measured from upper left corner=0)
    181            String X;
    182            String Y;
    183
    184            if ( is130 ) {
    185                X = "I";
    186                Y = "J";
    187            } else {
    188                X = "X";
    189                Y = "Y";
    190            }
    191
    192            String xstring = model.remove( X );
    193            String ystring = model.remove( Y );
    194
    195            java.awt.Point clickPoint = null;
    196            if ( ( xstring != null ) && ( ystring != null ) ) {
    197                try {
    198                    int x = Integer.parseInt( xstring.trim() );
    199                    int y = Integer.parseInt( ystring.trim() );
    200                    clickPoint = new java.awt.Point( x, y );
    201                } catch ( NumberFormatException nfe ) {
    202                    LOGGER.logError( nfe.getLocalizedMessage(), nfe );
    203                    throw new OGCWebServiceException( "GetFeatureInfo", "Invalid point parameter",
    204                                                      ExceptionCode.INVALID_POINT );
    205                }
    206            } else {
    207                throw new InconsistentRequestException( X + "- and/or " + Y
    208                                                        + "-value must be set in the GetFeatureInfo request" );
    209            }
    210
    211            // EXCEPTIONS (default=application/vnd.ogc.se_xml)
    212            String exceptions = model.get( "EXCEPTIONS" );
    213            if ( exceptions == null ) {
    214                if ( is130 ) {
    215                    exceptions = "XML";
    216                } else {
    217                    exceptions = "application/vnd.ogc.se_xml";
    218                }
    219            }
    220
    221            // <map_request_copy>
    222            GetMap getMapRequestCopy = null;
    223
    224            try {
    225                getMapRequestCopy = GetMap.create( model );
    226            } catch ( Exception ex ) {
    227                throw new InconsistentRequestException(
    228                                                        "\nAn Exception "
    229                                                                                + "occured in creating the GetMap request-copy included in the "
    230                                                                                + "GetFeatureInfo-Operations:\n"
    231                                                                                + "--> Location: WMSProtocolFactory, createGetFeatureInfoRequest(int, HashMap)\n"
    232                                                                                + ex.getMessage() );
    233
    234            }
    235
    236            // check for consistency
    237            if ( clickPoint.x > getMapRequestCopy.getWidth() || clickPoint.y > getMapRequestCopy.getHeight() ) {
    238                throw new InvalidPointException( "The requested point is not valid." );
    239            }
    240
    241            // VendorSpecificParameter; because all defined parameters has been
    242            // removed
    243            // from the model the vendorSpecificParameters are what left
    244            Map<String, String> vendorSpecificParameter = model;
    245
    246            // StyledLayerDescriptor
    247            StyledLayerDescriptor sld = getMapRequestCopy.getStyledLayerDescriptor();
    248
    249            GetFeatureInfo res = create( version, id, queryLayers, getMapRequestCopy, infoFormat, featureCount, clickPoint,
    250                                         exceptions, sld, vendorSpecificParameter );
    251            res.infoFormatIsDefault = infoFormatDefault;
    252
    253            return res;
    254        }
    255
    256        /**
    257         * Creates a new WMSFeatureInfoRequest_Impl object.
    258         *
    259         * @param version
    260         * @param id
    261         * @param queryLayers
    262         * @param getMapRequestCopy
    263         * @param infoFormat
    264         * @param featureCount
    265         * @param clickPoint
    266         * @param exceptions
    267         * @param sld
    268         * @param vendorSpecificParameter
    269         */
    270        private GetFeatureInfo( String version, String id, String[] queryLayers, GetMap getMapRequestCopy,
    271                                String infoFormat, int featureCount, Point clickPoint, String exceptions,
    272                                StyledLayerDescriptor sld, Map<String, String> vendorSpecificParameter ) {
    273            super( version, id, vendorSpecificParameter );
    274            this.queryLayers = new ArrayList<String>();
    275            setQueryLayers( queryLayers );
    276            setGetMapRequestCopy( getMapRequestCopy );
    277            setGetMapRequestCopy( getMapRequestCopy );
    278            setFeatureCount( featureCount );
    279            setClickPoint( clickPoint );
    280            setExceptions( exceptions );
    281            setStyledLayerDescriptor( sld );
    282            setInfoFormat( infoFormat );
    283        }
    284
    285        /**
    286         * <map request copy> is not a name/value pair like the other parameters. Instead, most of the GetMap request
    287         * parameters that generated the original map are repeated. Two are omitted because GetFeatureInfo provides its own
    288         * values: VERSION and REQUEST. The remainder of the GetMap request shall be embedded contiguously in the
    289         * GetFeatureInfo request.
    290         *
    291         * @return a copy of the original request
    292         */
    293        public GetMap getGetMapRequestCopy() {
    294            return getMapRequestCopy;
    295        }
    296
    297        /**
    298         * sets the <GetMapRequestCopy>
    299         *
    300         * @param getMapRequestCopy
    301         */
    302        public void setGetMapRequestCopy( GetMap getMapRequestCopy ) {
    303            this.getMapRequestCopy = getMapRequestCopy;
    304        }
    305
    306        /**
    307         * The required QUERY_LAYERS parameter states the map layer(s) from which feature information is desired to be
    308         * retrieved. Its value is a comma- separated list of one or more map layers that are returned as an array. This
    309         * parameter shall contain at least one layer name, but may contain fewer layers than the original GetMap request.
    310         * <p>
    311         * </p>
    312         * If any layer in this list is not contained in the Capabilities XML of the WMS, the results are undefined and the
    313         * WMS shall produce an exception response.
    314         *
    315         * @return the layer names
    316         */
    317        public String[] getQueryLayers() {
    318            return queryLayers.toArray( new String[queryLayers.size()] );
    319        }
    320
    321        /**
    322         * adds the <QueryLayers>
    323         *
    324         * @param queryLayers
    325         */
    326        public void addQueryLayers( String queryLayers ) {
    327            this.queryLayers.add( queryLayers );
    328        }
    329
    330        /**
    331         * sets the <QueryLayers>
    332         *
    333         * @param queryLayers
    334         */
    335        public void setQueryLayers( String[] queryLayers ) {
    336            this.queryLayers.clear();
    337
    338            if ( queryLayers != null ) {
    339                for ( int i = 0; i < queryLayers.length; i++ ) {
    340                    this.queryLayers.add( queryLayers[i] );
    341                }
    342            }
    343        }
    344
    345        /**
    346         * The optional INFO_FORMAT indicates what format to use when returning the feature information. Supported values
    347         * for a GetFeatureInfo request on a WMS instance are listed as MIME types in one or more <Format>elements inside
    348         * the <Request><FeatureInfo>element of its Capabilities XML. The entire MIME type string in <Format>is used as the
    349         * value of the INFO_FORMAT parameter. In an HTTP environment, the MIME type shall be set on the returned object
    350         * using the Content-type entity header.
    351         * <p>
    352         * </p>
    353         * <b>EXAMPLE: </b> <tt> The parameter INFO_FORMAT=application/vnd.ogc.gml
    354         * requests that the feature information be formatted in Geography Markup
    355         * Language (GML).</tt>
    356         *
    357         * @return the format
    358         */
    359        public String getInfoFormat() {
    360            return infoFormat;
    361        }
    362
    363        /**
    364         * sets the <InfoFormat>
    365         *
    366         * @param infoFormat
    367         */
    368        public void setInfoFormat( String infoFormat ) {
    369            this.infoFormat = infoFormat;
    370        }
    371
    372        /**
    373         * The optional FEATURE_COUNT parameter states the maximum number of features for which feature information should
    374         * be returned. Its value is a positive integer greater than zero. The default value is 1 if this parameter is
    375         * omitted.
    376         *
    377         * @return the count
    378         */
    379        public int getFeatureCount() {
    380            return featureCount;
    381        }
    382
    383        /**
    384         * sets the <FeatureCount>
    385         *
    386         * @param featureCount
    387         */
    388        public void setFeatureCount( int featureCount ) {
    389            this.featureCount = featureCount;
    390        }
    391
    392        /**
    393         * The required X and Y parameters indicate a point of interest on the map. X and Y identify a single point within
    394         * the borders of the WIDTH and HEIGHT parameters of the embedded GetMap request. The origin is set to (0,0)
    395         * centered in the pixel at the upper left corner; X increases to the right and Y increases downward. X and Y are
    396         * retruned as java.awt.Point class/datastructure.
    397         *
    398         * @return the point of interest
    399         */
    400        public Point getClickPoint() {
    401            return clickPoint;
    402        }
    403
    404        /**
    405         * sets the <ClickPoint>
    406         *
    407         * @param clickPoint
    408         */
    409        public void setClickPoint( Point clickPoint ) {
    410            this.clickPoint = clickPoint;
    411        }
    412
    413        /**
    414         * The optional EXCEPTIONS parameter states the manner in which errors are to be reported to the client. The default
    415         * value is application/vnd.ogc.se_xml if this parameter is absent from the request. At present, not other values
    416         * are defined for the WMS GetFeatureInfo request.
    417         *
    418         * @return the exception format
    419         */
    420        public String getExceptions() {
    421            return exceptions;
    422        }
    423
    424        /**
    425         * sets the <Exception>
    426         *
    427         * @param exceptions
    428         */
    429        public void setExceptions( String exceptions ) {
    430            this.exceptions = exceptions;
    431        }
    432
    433        /**
    434         * returns the SLD the request is made of. This implies that a 'simple' HTTP GET-Request will be transformed into a
    435         * valid SLD. This is mandatory within a JaGo WMS.
    436         * <p>
    437         * </p>
    438         * This mean even if a GetMap request is send using the HTTP GET method, an implementing class has to map the
    439         * request to a SLD data sructure.
    440         *
    441         * @return the sld
    442         */
    443        public StyledLayerDescriptor getStyledLayerDescriptor() {
    444            return sld;
    445        }
    446
    447        /**
    448         * sets the SLD the request is made of. This implies that a 'simple' HTTP GET-Request or a part of it will be
    449         * transformed into a valid SLD. For convenience it is asumed that the SLD names just a single layer to generate
    450         * display elements of.
    451         *
    452         * @param sld
    453         */
    454        public void setStyledLayerDescriptor( StyledLayerDescriptor sld ) {
    455            this.sld = sld;
    456        }
    457
    458        @Override
    459        public String toString() {
    460            try {
    461                return getRequestParameter();
    462            } catch ( OGCWebServiceException e ) {
    463                e.printStackTrace();
    464            }
    465            return super.toString();
    466        }
    467
    468        /**
    469         * returns the parameter of a HTTP GET request.
    470         *
    471         */
    472        @Override
    473        public String getRequestParameter()
    474                                throws OGCWebServiceException {
    475            // indicates if the request parameters are decoded as SLD. deegree won't
    476            // perform SLD requests through HTTP GET
    477            if ( ( getMapRequestCopy.getBoundingBox() == null ) || ( queryLayers.size() == 0 ) ) {
    478                throw new OGCWebServiceException( "Operations can't be expressed as HTTP GET request " );
    479            }
    480
    481            StringBuffer sb = new StringBuffer( "service=WMS" );
    482
    483            if ( getVersion().compareTo( "1.0.0" ) <= 0 ) {
    484                sb.append( "&VERSION=" + getVersion() + "&REQUEST=feature_info" );
    485                sb.append( "&TRANSPARENT=" + getMapRequestCopy.getTransparency() );
    486            } else {
    487                sb.append( "&VERSION=" + getVersion() + "&REQUEST=GetFeatureInfo" );
    488                sb.append( "&TRANSPARENCY=" + getMapRequestCopy.getTransparency() );
    489            }
    490
    491            sb.append( "&WIDTH=" + getMapRequestCopy.getWidth() );
    492            sb.append( "&HEIGHT=" + getMapRequestCopy.getHeight() );
    493            sb.append( "&FORMAT=" + getMapRequestCopy.getFormat() );
    494            sb.append( "&EXCEPTIONS=" + getExceptions() );
    495            sb.append( "&BGCOLOR=" );
    496            sb.append( ColorUtils.toHexCode( "0x", getMapRequestCopy.getBGColor() ) );
    497            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
    498                sb.append( "&CRS=" + getMapRequestCopy.getSrs() );
    499                sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getY() );
    500                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getX() );
    501                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() );
    502                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() );
    503            } else {
    504                sb.append( "&SRS=" + getMapRequestCopy.getSrs() );
    505                sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getX() );
    506                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getY() );
    507                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() );
    508                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() );
    509            }
    510
    511            GetMap.Layer[] layers = getMapRequestCopy.getLayers();
    512            String l = "";
    513            String s = "";
    514
    515            for ( int i = 0; i < layers.length; i++ ) {
    516                l += ( layers[i].getName() + "," );
    517                s += ( layers[i].getStyleName() + "," );
    518            }
    519
    520            l = l.substring( 0, l.length() - 1 );
    521            s = s.substring( 0, s.length() - 1 );
    522            sb.append( "&LAYERS=" + l );
    523
    524            // replace $DEFAULT with "", which is what WMSses expect
    525            s = StringTools.replace( s, "$DEFAULT", "", true );
    526
    527            sb.append( "&STYLES=" + s );
    528
    529            // TODO
    530            // append time, elevation and sample dimension
    531
    532            String[] qlayers = getQueryLayers();
    533            String ql = "";
    534
    535            for ( int i = 0; i < qlayers.length; i++ ) {
    536                ql += ( qlayers[i] + "," );
    537            }
    538
    539            ql = ql.substring( 0, ql.length() - 1 );
    540            sb.append( "&QUERY_LAYERS=" + ql );
    541            sb.append( "&FEATURE_COUNT=" + getFeatureCount() );
    542            sb.append( "&INFO_FORMAT=" + getInfoFormat() );
    543            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
    544                sb.append( "&I=" + clickPoint.x );
    545                sb.append( "&J=" + clickPoint.y );
    546            } else {
    547                sb.append( "&X=" + clickPoint.x );
    548                sb.append( "&Y=" + clickPoint.y );
    549            }
    550
    551            return sb.toString();
    552        }
    553
    554        /**
    555         * @return whether the info format is the default setting
    556         */
    557        public boolean isInfoFormatDefault() {
    558            return infoFormatIsDefault;
    559        }
    560
    561    }