001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wms/operation/GetFeatureInfo.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 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: 9348 $ $Date: 2007-12-27 17:59:14 +0100 (Do, 27 Dez 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       
130            return new GetFeatureInfo( version, id, queryLayers, getMapRequestCopy,
131                                                     infoFormat, featureCount, clickPoint, exceptions,
132                                                     sld, vendorSpecificParameter );
133    
134    
135        }
136    
137        /**
138         * creates a <tt>WMSFeatureInfoRequest</tt> from a <tt>HashMap</tt> that
139         * contains the request parameters as key-value-pairs. Keys are expected to
140         * be in upper case notation.
141         * 
142         * @param model
143         *            <tt>HashMap</tt> containing the request parameters
144         * @return an instance of <tt>WMSFeatureInfoRequest</tt>
145         * @throws OGCWebServiceException 
146         */
147        public static GetFeatureInfo create( Map<String, String> model )
148                                throws OGCWebServiceException {
149         
150            // VERSION
151            String version = model.get( "VERSION" );
152            if ( version == null ) {
153                version = model.get( "WMTVER" );
154            }
155            if ( version == null ) {
156                throw new InconsistentRequestException(
157                                                        "VERSION-value must be set in the GetFeatureInfo request" );
158            }
159    
160            boolean is130 = ( "1.3.0".compareTo( version ) <= 0 );
161    
162            // ID
163            String id = model.get( "ID" );
164            if ( id == null ) {
165                throw new InconsistentRequestException(
166                                                        "ID-value must be set in the GetFeatureInfo request" );
167            }
168    
169            // QUERY_LAYERS
170            String layerlist = model.remove( "QUERY_LAYERS" );
171            String[] queryLayers = null;
172    
173            if ( layerlist != null ) {
174                StringTokenizer st = new StringTokenizer( layerlist, "," );
175                queryLayers = new String[st.countTokens()];
176                int i = 0;
177                while ( st.hasMoreTokens() ) {
178                    queryLayers[i++] = st.nextToken();
179                }
180            } else {
181                throw new InconsistentRequestException(
182                                                        "QUERY_LAYERS-value must be set in the GetFeatureInfo request" );
183            }
184    
185            // INFO_FORMAT (mime-type)
186            String infoFormat = model.remove( "INFO_FORMAT" );
187            boolean infoFormatDefault = false;
188            if ( infoFormat == null ) {
189                infoFormat = "application/vnd.ogc.gml";
190                infoFormatDefault = true;
191            }
192    
193            // FEATURE_COUNT (default=1)
194            String feco = model.remove( "FEATURE_COUNT" );
195            int featureCount = 1;
196            if ( feco != null ) {
197                featureCount = Integer.parseInt( feco.trim() );
198            }
199            if ( featureCount < 0 ) {
200                featureCount = 1;
201            }
202    
203            // X, Y (measured from upper left corner=0)
204            String X;
205            String Y;
206    
207            if ( is130 ) {
208                X = "I";
209                Y = "J";
210            } else {
211                X = "X";
212                Y = "Y";
213            }
214    
215            String xstring = model.remove( X );
216            String ystring = model.remove( Y );
217    
218            java.awt.Point clickPoint = null;
219            if ( ( xstring != null ) & ( ystring != null ) ) {
220                try {
221                    int x = Integer.parseInt( xstring.trim() );
222                    int y = Integer.parseInt( ystring.trim() );
223                    clickPoint = new java.awt.Point( x, y );
224                } catch ( NumberFormatException nfe ) {
225                    LOGGER.logError( nfe.getLocalizedMessage(), nfe );
226                    throw new OGCWebServiceException( "GetFeatureInfo", "Invalid point parameter",
227                                                      ExceptionCode.INVALID_POINT );
228                }
229            } else {
230                throw new InconsistentRequestException(
231                                                        X
232                                                                                + "- and/or "
233                                                                                + Y
234                                                                                + "-value must be set in the GetFeatureInfo request" );
235            }
236    
237            // EXCEPTIONS (default=application/vnd.ogc.se_xml)
238            String exceptions = model.get( "EXCEPTIONS" );
239            if ( exceptions == null ) {
240                if ( is130 ) {
241                    exceptions = "XML";
242                } else {
243                    exceptions = "application/vnd.ogc.se_xml";
244                }
245            }
246    
247            //  <map_request_copy>
248            GetMap getMapRequestCopy = null;
249    
250            try {
251                getMapRequestCopy = GetMap.create( model );
252            } catch ( Exception ex ) {
253                throw new InconsistentRequestException(
254                                                        "\nAn Exception "
255                                                                                + "occured in creating the GetMap request-copy included in the "
256                                                                                + "GetFeatureInfo-Operations:\n"
257                                                                                + "--> Location: WMSProtocolFactory, createGetFeatureInfoRequest(int, HashMap)\n"
258                                                                                + ex.getMessage() );
259    
260            }
261    
262            // check for consistency
263            if ( clickPoint.x > getMapRequestCopy.getWidth()
264                 || clickPoint.y > getMapRequestCopy.getHeight() ) {
265                throw new InvalidPointException( "The requested point is not valid." );
266            }
267    
268            // VendorSpecificParameter; because all defined parameters has been
269            // removed
270            // from the model the vendorSpecificParameters are what left
271            Map<String, String> vendorSpecificParameter = model;
272    
273            // StyledLayerDescriptor
274            StyledLayerDescriptor sld = getMapRequestCopy.getStyledLayerDescriptor();
275    
276            GetFeatureInfo res = create( version, id, queryLayers, getMapRequestCopy, infoFormat,
277                                         featureCount, clickPoint, exceptions, sld,
278                                         vendorSpecificParameter );
279            res.infoFormatIsDefault = infoFormatDefault;
280    
281            return res;
282        }
283    
284        /**
285         * Creates a new WMSFeatureInfoRequest_Impl object.
286         * 
287         * @param version
288         * @param id
289         * @param queryLayers
290         * @param getMapRequestCopy
291         * @param infoFormat
292         * @param featureCount
293         * @param clickPoint
294         * @param exceptions
295         * @param sld
296         * @param vendorSpecificParameter
297         */
298        private GetFeatureInfo( String version, String id, String[] queryLayers,
299                               GetMap getMapRequestCopy, String infoFormat, int featureCount,
300                               Point clickPoint, String exceptions, StyledLayerDescriptor sld,
301                               Map<String, String> vendorSpecificParameter ) {
302            super( version, id, vendorSpecificParameter );
303            this.queryLayers = new ArrayList<String>();
304            setQueryLayers( queryLayers );
305            setGetMapRequestCopy( getMapRequestCopy );
306            setGetMapRequestCopy( getMapRequestCopy );
307            setFeatureCount( featureCount );
308            setClickPoint( clickPoint );
309            setExceptions( exceptions );
310            setStyledLayerDescriptor( sld );
311            setInfoFormat( infoFormat );
312        }
313    
314        /**
315         * <map request copy> is not a name/value pair like the other parameters.
316         * Instead, most of the GetMap request parameters that generated the
317         * original map are repeated. Two are omitted because GetFeatureInfo
318         * provides its own values: VERSION and REQUEST. The remainder of the GetMap
319         * request shall be embedded contiguously in the GetFeatureInfo request.
320         * @return a copy of the original request
321         */
322        public GetMap getGetMapRequestCopy() {
323            return getMapRequestCopy;
324        }
325    
326        /**
327         * sets the <GetMapRequestCopy>
328         * @param getMapRequestCopy 
329         */
330        public void setGetMapRequestCopy( GetMap getMapRequestCopy ) {
331            this.getMapRequestCopy = getMapRequestCopy;
332        }
333    
334        /**
335         * The required QUERY_LAYERS parameter states the map layer(s) from which
336         * feature information is desired to be retrieved. Its value is a comma-
337         * separated list of one or more map layers that are returned as an array.
338         * This parameter shall contain at least one layer name, but may contain
339         * fewer layers than the original GetMap request.
340         * <p>
341         * </p>
342         * If any layer in this list is not contained in the Capabilities XML of the
343         * WMS, the results are undefined and the WMS shall produce an exception
344         * response.
345         * @return the layer names
346         */
347        public String[] getQueryLayers() {
348            return queryLayers.toArray( new String[queryLayers.size()] );
349        }
350    
351        /**
352         * adds the <QueryLayers>
353         * @param queryLayers 
354         */
355        public void addQueryLayers( String queryLayers ) {
356            this.queryLayers.add( queryLayers );
357        }
358    
359        /**
360         * sets the <QueryLayers>
361         * @param queryLayers 
362         */
363        public void setQueryLayers( String[] queryLayers ) {
364            this.queryLayers.clear();
365    
366            if ( queryLayers != null ) {
367                for ( int i = 0; i < queryLayers.length; i++ ) {
368                    this.queryLayers.add( queryLayers[i] );
369                }
370            }
371        }
372    
373        /**
374         * The optional INFO_FORMAT indicates what format to use when returning the
375         * feature information. Supported values for a GetFeatureInfo request on a
376         * WMS instance are listed as MIME types in one or more <Format>elements
377         * inside the <Request><FeatureInfo>element of its Capabilities XML. The
378         * entire MIME type string in <Format>is used as the value of the
379         * INFO_FORMAT parameter. In an HTTP environment, the MIME type shall be set
380         * on the returned object using the Content-type entity header.
381         * <p>
382         * </p>
383         * <b>EXAMPLE: </b> <tt> The parameter INFO_FORMAT=application/vnd.ogc.gml
384         * requests that the feature information be formatted in Geography Markup
385         * Language (GML).</tt>
386         * @return the format
387         */
388        public String getInfoFormat() {
389            return infoFormat;
390        }
391    
392        /**
393         * sets the <InfoFormat>
394         * @param infoFormat 
395         */
396        public void setInfoFormat( String infoFormat ) {
397            this.infoFormat = infoFormat;
398        }
399    
400        /**
401         * The optional FEATURE_COUNT parameter states the maximum number of
402         * features for which feature information should be returned. Its value is a
403         * positive integer greater than zero. The default value is 1 if this
404         * parameter is omitted.
405         * @return the count
406         */
407        public int getFeatureCount() {
408            return featureCount;
409        }
410    
411        /**
412         * sets the <FeatureCount>
413         * @param featureCount 
414         */
415        public void setFeatureCount( int featureCount ) {
416            this.featureCount = featureCount;
417        }
418    
419        /**
420         * The required X and Y parameters indicate a point of interest on the map.
421         * X and Y identify a single point within the borders of the WIDTH and
422         * HEIGHT parameters of the embedded GetMap request. The origin is set to
423         * (0,0) centered in the pixel at the upper left corner; X increases to the
424         * right and Y increases downward. X and Y are retruned as java.awt.Point
425         * class/datastructure.
426         * @return the point of interest
427         */
428        public Point getClickPoint() {
429            return clickPoint;
430        }
431    
432        /**
433         * sets the <ClickPoint>
434         * @param clickPoint 
435         */
436        public void setClickPoint( Point clickPoint ) {
437            this.clickPoint = clickPoint;
438        }
439    
440        /**
441         * The optional EXCEPTIONS parameter states the manner in which errors are
442         * to be reported to the client. The default value is
443         * application/vnd.ogc.se_xml if this parameter is absent from the request.
444         * At present, not other values are defined for the WMS GetFeatureInfo
445         * request.
446         * @return the exception format
447         */
448        public String getExceptions() {
449            return exceptions;
450        }
451    
452        /**
453         * sets the <Exception>
454         * @param exceptions 
455         */
456        public void setExceptions( String exceptions ) {
457            this.exceptions = exceptions;
458        }
459    
460        /**
461         * returns the SLD the request is made of. This implies that a 'simple' HTTP
462         * GET-Request will be transformed into a valid SLD. This is mandatory
463         * within a JaGo WMS.
464         * <p>
465         * </p>
466         * This mean even if a GetMap request is send using the HTTP GET method, an
467         * implementing class has to map the request to a SLD data sructure.
468         * @return the sld
469         */
470        public StyledLayerDescriptor getStyledLayerDescriptor() {
471            return sld;
472        }
473    
474        /**
475         * sets the SLD the request is made of. This implies that a 'simple' HTTP
476         * GET-Request or a part of it will be transformed into a valid SLD. For
477         * convenience it is asumed that the SLD names just a single layer to
478         * generate display elements of.
479         * @param sld 
480         */
481        public void setStyledLayerDescriptor( StyledLayerDescriptor sld ) {
482            this.sld = sld;
483        }
484    
485        @Override
486        public String toString() {
487            try {
488                return getRequestParameter();
489            } catch ( OGCWebServiceException e ) {
490                e.printStackTrace();
491            }
492            return super.toString();
493        }
494    
495        /**
496         * returns the parameter of a HTTP GET request.
497         *  
498         */
499        @Override
500        public String getRequestParameter()
501                                throws OGCWebServiceException {
502            // indicates if the request parameters are decoded as SLD. deegree won't
503            // perform SLD requests through HTTP GET
504            if ( ( getMapRequestCopy.getBoundingBox() == null ) || ( queryLayers.size() == 0 ) ) {
505                throw new OGCWebServiceException( "Operations can't be expressed as HTTP GET request " );
506            }
507    
508            StringBuffer sb = new StringBuffer( "service=WMS" );
509    
510            if ( getVersion().compareTo( "1.0.0" ) <= 0 ) {
511                sb.append( "&VERSION=" + getVersion() + "&REQUEST=feature_info" );
512                sb.append( "&TRANSPARENT=" + getMapRequestCopy.getTransparency() );
513            } else {
514                sb.append( "&VERSION=" + getVersion() + "&REQUEST=GetFeatureInfo" );
515                sb.append( "&TRANSPARENCY=" + getMapRequestCopy.getTransparency() );
516            }
517    
518            sb.append( "&WIDTH=" + getMapRequestCopy.getWidth() );
519            sb.append( "&HEIGHT=" + getMapRequestCopy.getHeight() );
520            sb.append( "&FORMAT=" + getMapRequestCopy.getFormat() );
521            sb.append( "&EXCEPTIONS=" + getExceptions() );
522            sb.append( "&BGCOLOR=" );
523            sb.append( ColorUtils.toHexCode( "0x", getMapRequestCopy.getBGColor() ) );
524            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
525                sb.append( "&CRS=" + getMapRequestCopy.getSrs() );
526                sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getY() );
527                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getX() );
528                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() );
529                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() );
530            } else {
531                sb.append( "&SRS=" + getMapRequestCopy.getSrs() );
532                sb.append( "&BBOX=" ).append( getMapRequestCopy.getBoundingBox().getMin().getX() );
533                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMin().getY() );
534                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getX() );
535                sb.append( ',' ).append( getMapRequestCopy.getBoundingBox().getMax().getY() );
536            }
537    
538            GetMap.Layer[] layers = getMapRequestCopy.getLayers();
539            String l = "";
540            String s = "";
541    
542            for ( int i = 0; i < layers.length; i++ ) {
543                l += ( layers[i].getName() + "," );
544                s += ( layers[i].getStyleName() + "," );
545            }
546    
547            l = l.substring( 0, l.length() - 1 );
548            s = s.substring( 0, s.length() - 1 );
549            sb.append( "&LAYERS=" + l );
550    
551            // replace $DEFAULT with "", which is what WMSses expect
552            StringTools.replace( s, "$DEFAULT", "", true );
553    
554            sb.append( "&STYLES=" + s );
555    
556            // TODO
557            // append time, elevation and sample dimension 
558    
559            String[] qlayers = getQueryLayers();
560            String ql = "";
561    
562            for ( int i = 0; i < qlayers.length; i++ ) {
563                ql += ( qlayers[i] + "," );
564            }
565    
566            ql = ql.substring( 0, ql.length() - 1 );
567            sb.append( "&QUERY_LAYERS=" + ql );
568            sb.append( "&FEATURE_COUNT=" + getFeatureCount() );
569            sb.append( "&INFO_FORMAT=" + getInfoFormat() );
570            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
571                sb.append( "&I=" + clickPoint.x );
572                sb.append( "&J=" + clickPoint.y );
573            } else {
574                sb.append( "&X=" + clickPoint.x );
575                sb.append( "&Y=" + clickPoint.y );
576            }
577    
578            return sb.toString();
579        }
580    
581        /**
582         * @return whether the info format is the default setting
583         */
584        public boolean isInfoFormatDefault() {
585            return infoFormatIsDefault;
586        }
587    
588    }