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