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