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 }