001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }