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 * <map_request_copy> (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 ********************************************************************** */