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