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