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