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 }