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 }