001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/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 08:59:14 -0800 (Thu, 27 Dec 2007) $
065: */
066: public class GetFeatureInfo extends WMSRequestBase {
067:
068: private static final long serialVersionUID = 1197866346790857492L;
069:
070: private static final ILogger LOGGER = LoggerFactory
071: .getLogger(GetFeatureInfo.class);
072:
073: private List<String> queryLayers = null;
074:
075: private Point clickPoint = null;
076:
077: private String exceptions = null;
078:
079: private String infoFormat = null;
080:
081: private StyledLayerDescriptor sld = null;
082:
083: private GetMap getMapRequestCopy = null;
084:
085: private int featureCount = 1;
086:
087: private boolean infoFormatIsDefault = false;
088:
089: /**
090: * creates a <tt>WMSFeatureInfoRequest</tt> from the request parameters.
091: *
092: * @return an instance of <tt>WMSFeatureInfoRequest</tt>
093: * @param version
094: * VERSION=version (R): Request version.
095: * @param id the request id
096: * @param queryLayers
097: * QUERY_LAYERS=layer_list (R): Comma-separated list of one or
098: * more layers to be queried.
099: * @param getMapRequestCopy
100: * <map_request_copy> (R): Partial copy of the Map request
101: * parameters that generated the map for which information is
102: * desired.
103: * @param infoFormat
104: * INFO_FORMAT=output_format (O): Return format of feature
105: * information (MIME type).
106: * @param featureCount
107: * FEATURE_COUNT=number (O): Number of features about which to
108: * return information (default=1).
109: * @param clickPoint
110: * X=pixel_column (R): X coordinate in pixels of feature
111: * (measured from upper left corner=0) Y=pixel_row (R): Y
112: * coordinate in pixels of feature (measured from upper left
113: * corner=0)
114: * @param exceptions
115: * EXCEPTIONS=exception_format (O): The format in which
116: * exceptions are to be reported by the WMS
117: * (default=application/vnd.ogc.se_xml).
118: * @param sld
119: * StyledLayerDescriptor
120: * @param vendorSpecificParameter
121: * Vendor-specific parameters (O): Optional experimental
122: * parameters.
123: */
124: public static GetFeatureInfo create(String version, String id,
125: String[] queryLayers, GetMap getMapRequestCopy,
126: String infoFormat, int featureCount,
127: java.awt.Point clickPoint, String exceptions,
128: StyledLayerDescriptor sld,
129: Map<String, String> vendorSpecificParameter) {
130:
131: return new GetFeatureInfo(version, id, queryLayers,
132: getMapRequestCopy, infoFormat, featureCount,
133: clickPoint, exceptions, sld, vendorSpecificParameter);
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",
227: "Invalid point parameter",
228: ExceptionCode.INVALID_POINT);
229: }
230: } else {
231: throw new InconsistentRequestException(
232: X
233: + "- and/or "
234: + Y
235: + "-value must be set in the GetFeatureInfo request");
236: }
237:
238: // EXCEPTIONS (default=application/vnd.ogc.se_xml)
239: String exceptions = model.get("EXCEPTIONS");
240: if (exceptions == null) {
241: if (is130) {
242: exceptions = "XML";
243: } else {
244: exceptions = "application/vnd.ogc.se_xml";
245: }
246: }
247:
248: // <map_request_copy>
249: GetMap getMapRequestCopy = null;
250:
251: try {
252: getMapRequestCopy = GetMap.create(model);
253: } catch (Exception ex) {
254: throw new InconsistentRequestException(
255: "\nAn Exception "
256: + "occured in creating the GetMap request-copy included in the "
257: + "GetFeatureInfo-Operations:\n"
258: + "--> Location: WMSProtocolFactory, createGetFeatureInfoRequest(int, HashMap)\n"
259: + ex.getMessage());
260:
261: }
262:
263: // check for consistency
264: if (clickPoint.x > getMapRequestCopy.getWidth()
265: || clickPoint.y > getMapRequestCopy.getHeight()) {
266: throw new InvalidPointException(
267: "The requested point is not valid.");
268: }
269:
270: // VendorSpecificParameter; because all defined parameters has been
271: // removed
272: // from the model the vendorSpecificParameters are what left
273: Map<String, String> vendorSpecificParameter = model;
274:
275: // StyledLayerDescriptor
276: StyledLayerDescriptor sld = getMapRequestCopy
277: .getStyledLayerDescriptor();
278:
279: GetFeatureInfo res = create(version, id, queryLayers,
280: getMapRequestCopy, infoFormat, featureCount,
281: clickPoint, exceptions, sld, vendorSpecificParameter);
282: res.infoFormatIsDefault = infoFormatDefault;
283:
284: return res;
285: }
286:
287: /**
288: * Creates a new WMSFeatureInfoRequest_Impl object.
289: *
290: * @param version
291: * @param id
292: * @param queryLayers
293: * @param getMapRequestCopy
294: * @param infoFormat
295: * @param featureCount
296: * @param clickPoint
297: * @param exceptions
298: * @param sld
299: * @param vendorSpecificParameter
300: */
301: private GetFeatureInfo(String version, String id,
302: String[] queryLayers, GetMap getMapRequestCopy,
303: String infoFormat, int featureCount, Point clickPoint,
304: String exceptions, StyledLayerDescriptor sld,
305: Map<String, String> vendorSpecificParameter) {
306: super (version, id, vendorSpecificParameter);
307: this .queryLayers = new ArrayList<String>();
308: setQueryLayers(queryLayers);
309: setGetMapRequestCopy(getMapRequestCopy);
310: setGetMapRequestCopy(getMapRequestCopy);
311: setFeatureCount(featureCount);
312: setClickPoint(clickPoint);
313: setExceptions(exceptions);
314: setStyledLayerDescriptor(sld);
315: setInfoFormat(infoFormat);
316: }
317:
318: /**
319: * <map request copy> is not a name/value pair like the other parameters.
320: * Instead, most of the GetMap request parameters that generated the
321: * original map are repeated. Two are omitted because GetFeatureInfo
322: * provides its own values: VERSION and REQUEST. The remainder of the GetMap
323: * request shall be embedded contiguously in the GetFeatureInfo request.
324: * @return a copy of the original request
325: */
326: public GetMap getGetMapRequestCopy() {
327: return getMapRequestCopy;
328: }
329:
330: /**
331: * sets the <GetMapRequestCopy>
332: * @param getMapRequestCopy
333: */
334: public void setGetMapRequestCopy(GetMap getMapRequestCopy) {
335: this .getMapRequestCopy = getMapRequestCopy;
336: }
337:
338: /**
339: * The required QUERY_LAYERS parameter states the map layer(s) from which
340: * feature information is desired to be retrieved. Its value is a comma-
341: * separated list of one or more map layers that are returned as an array.
342: * This parameter shall contain at least one layer name, but may contain
343: * fewer layers than the original GetMap request.
344: * <p>
345: * </p>
346: * If any layer in this list is not contained in the Capabilities XML of the
347: * WMS, the results are undefined and the WMS shall produce an exception
348: * response.
349: * @return the layer names
350: */
351: public String[] getQueryLayers() {
352: return queryLayers.toArray(new String[queryLayers.size()]);
353: }
354:
355: /**
356: * adds the <QueryLayers>
357: * @param queryLayers
358: */
359: public void addQueryLayers(String queryLayers) {
360: this .queryLayers.add(queryLayers);
361: }
362:
363: /**
364: * sets the <QueryLayers>
365: * @param queryLayers
366: */
367: public void setQueryLayers(String[] queryLayers) {
368: this .queryLayers.clear();
369:
370: if (queryLayers != null) {
371: for (int i = 0; i < queryLayers.length; i++) {
372: this .queryLayers.add(queryLayers[i]);
373: }
374: }
375: }
376:
377: /**
378: * The optional INFO_FORMAT indicates what format to use when returning the
379: * feature information. Supported values for a GetFeatureInfo request on a
380: * WMS instance are listed as MIME types in one or more <Format>elements
381: * inside the <Request><FeatureInfo>element of its Capabilities XML. The
382: * entire MIME type string in <Format>is used as the value of the
383: * INFO_FORMAT parameter. In an HTTP environment, the MIME type shall be set
384: * on the returned object using the Content-type entity header.
385: * <p>
386: * </p>
387: * <b>EXAMPLE: </b> <tt> The parameter INFO_FORMAT=application/vnd.ogc.gml
388: * requests that the feature information be formatted in Geography Markup
389: * Language (GML).</tt>
390: * @return the format
391: */
392: public String getInfoFormat() {
393: return infoFormat;
394: }
395:
396: /**
397: * sets the <InfoFormat>
398: * @param infoFormat
399: */
400: public void setInfoFormat(String infoFormat) {
401: this .infoFormat = infoFormat;
402: }
403:
404: /**
405: * The optional FEATURE_COUNT parameter states the maximum number of
406: * features for which feature information should be returned. Its value is a
407: * positive integer greater than zero. The default value is 1 if this
408: * parameter is omitted.
409: * @return the count
410: */
411: public int getFeatureCount() {
412: return featureCount;
413: }
414:
415: /**
416: * sets the <FeatureCount>
417: * @param featureCount
418: */
419: public void setFeatureCount(int featureCount) {
420: this .featureCount = featureCount;
421: }
422:
423: /**
424: * The required X and Y parameters indicate a point of interest on the map.
425: * X and Y identify a single point within the borders of the WIDTH and
426: * HEIGHT parameters of the embedded GetMap request. The origin is set to
427: * (0,0) centered in the pixel at the upper left corner; X increases to the
428: * right and Y increases downward. X and Y are retruned as java.awt.Point
429: * class/datastructure.
430: * @return the point of interest
431: */
432: public Point getClickPoint() {
433: return clickPoint;
434: }
435:
436: /**
437: * sets the <ClickPoint>
438: * @param clickPoint
439: */
440: public void setClickPoint(Point clickPoint) {
441: this .clickPoint = clickPoint;
442: }
443:
444: /**
445: * The optional EXCEPTIONS parameter states the manner in which errors are
446: * to be reported to the client. The default value is
447: * application/vnd.ogc.se_xml if this parameter is absent from the request.
448: * At present, not other values are defined for the WMS GetFeatureInfo
449: * request.
450: * @return the exception format
451: */
452: public String getExceptions() {
453: return exceptions;
454: }
455:
456: /**
457: * sets the <Exception>
458: * @param exceptions
459: */
460: public void setExceptions(String exceptions) {
461: this .exceptions = exceptions;
462: }
463:
464: /**
465: * returns the SLD the request is made of. This implies that a 'simple' HTTP
466: * GET-Request will be transformed into a valid SLD. This is mandatory
467: * within a JaGo WMS.
468: * <p>
469: * </p>
470: * This mean even if a GetMap request is send using the HTTP GET method, an
471: * implementing class has to map the request to a SLD data sructure.
472: * @return the sld
473: */
474: public StyledLayerDescriptor getStyledLayerDescriptor() {
475: return sld;
476: }
477:
478: /**
479: * sets the SLD the request is made of. This implies that a 'simple' HTTP
480: * GET-Request or a part of it will be transformed into a valid SLD. For
481: * convenience it is asumed that the SLD names just a single layer to
482: * generate display elements of.
483: * @param sld
484: */
485: public void setStyledLayerDescriptor(StyledLayerDescriptor sld) {
486: this .sld = sld;
487: }
488:
489: @Override
490: public String toString() {
491: try {
492: return getRequestParameter();
493: } catch (OGCWebServiceException e) {
494: e.printStackTrace();
495: }
496: return super .toString();
497: }
498:
499: /**
500: * returns the parameter of a HTTP GET request.
501: *
502: */
503: @Override
504: public String getRequestParameter() throws OGCWebServiceException {
505: // indicates if the request parameters are decoded as SLD. deegree won't
506: // perform SLD requests through HTTP GET
507: if ((getMapRequestCopy.getBoundingBox() == null)
508: || (queryLayers.size() == 0)) {
509: throw new OGCWebServiceException(
510: "Operations can't be expressed as HTTP GET request ");
511: }
512:
513: StringBuffer sb = new StringBuffer("service=WMS");
514:
515: if (getVersion().compareTo("1.0.0") <= 0) {
516: sb.append("&VERSION=" + getVersion()
517: + "&REQUEST=feature_info");
518: sb.append("&TRANSPARENT="
519: + getMapRequestCopy.getTransparency());
520: } else {
521: sb.append("&VERSION=" + getVersion()
522: + "&REQUEST=GetFeatureInfo");
523: sb.append("&TRANSPARENCY="
524: + getMapRequestCopy.getTransparency());
525: }
526:
527: sb.append("&WIDTH=" + getMapRequestCopy.getWidth());
528: sb.append("&HEIGHT=" + getMapRequestCopy.getHeight());
529: sb.append("&FORMAT=" + getMapRequestCopy.getFormat());
530: sb.append("&EXCEPTIONS=" + getExceptions());
531: sb.append("&BGCOLOR=");
532: sb.append(ColorUtils.toHexCode("0x", getMapRequestCopy
533: .getBGColor()));
534: if ("1.3.0".compareTo(getVersion()) <= 0) {
535: sb.append("&CRS=" + getMapRequestCopy.getSrs());
536: sb.append("&BBOX=").append(
537: getMapRequestCopy.getBoundingBox().getMin().getY());
538: sb.append(',').append(
539: getMapRequestCopy.getBoundingBox().getMin().getX());
540: sb.append(',').append(
541: getMapRequestCopy.getBoundingBox().getMax().getY());
542: sb.append(',').append(
543: getMapRequestCopy.getBoundingBox().getMax().getX());
544: } else {
545: sb.append("&SRS=" + getMapRequestCopy.getSrs());
546: sb.append("&BBOX=").append(
547: getMapRequestCopy.getBoundingBox().getMin().getX());
548: sb.append(',').append(
549: getMapRequestCopy.getBoundingBox().getMin().getY());
550: sb.append(',').append(
551: getMapRequestCopy.getBoundingBox().getMax().getX());
552: sb.append(',').append(
553: getMapRequestCopy.getBoundingBox().getMax().getY());
554: }
555:
556: GetMap.Layer[] layers = getMapRequestCopy.getLayers();
557: String l = "";
558: String s = "";
559:
560: for (int i = 0; i < layers.length; i++) {
561: l += (layers[i].getName() + ",");
562: s += (layers[i].getStyleName() + ",");
563: }
564:
565: l = l.substring(0, l.length() - 1);
566: s = s.substring(0, s.length() - 1);
567: sb.append("&LAYERS=" + l);
568:
569: // replace $DEFAULT with "", which is what WMSses expect
570: StringTools.replace(s, "$DEFAULT", "", true);
571:
572: sb.append("&STYLES=" + s);
573:
574: // TODO
575: // append time, elevation and sample dimension
576:
577: String[] qlayers = getQueryLayers();
578: String ql = "";
579:
580: for (int i = 0; i < qlayers.length; i++) {
581: ql += (qlayers[i] + ",");
582: }
583:
584: ql = ql.substring(0, ql.length() - 1);
585: sb.append("&QUERY_LAYERS=" + ql);
586: sb.append("&FEATURE_COUNT=" + getFeatureCount());
587: sb.append("&INFO_FORMAT=" + getInfoFormat());
588: if ("1.3.0".compareTo(getVersion()) <= 0) {
589: sb.append("&I=" + clickPoint.x);
590: sb.append("&J=" + clickPoint.y);
591: } else {
592: sb.append("&X=" + clickPoint.x);
593: sb.append("&Y=" + clickPoint.y);
594: }
595:
596: return sb.toString();
597: }
598:
599: /**
600: * @return whether the info format is the default setting
601: */
602: public boolean isInfoFormatDefault() {
603: return infoFormatIsDefault;
604: }
605:
606: }
|