0001: //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/ogcwebservices/wms/DefaultGetFeatureInfoHandler.java $
0002: /*---------------- FILE HEADER ------------------------------------------
0003:
0004: This file is part of deegree.
0005: Copyright (C) 2001-2008 by:
0006: EXSE, Department of Geography, University of Bonn
0007: http://www.giub.uni-bonn.de/deegree/
0008: lat/lon GmbH
0009: http://www.lat-lon.de
0010:
0011: This library is free software; you can redistribute it and/or
0012: modify it under the terms of the GNU Lesser General Public
0013: License as published by the Free Software Foundation; either
0014: version 2.1 of the License, or (at your option) any later version.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: Contact:
0026:
0027: Andreas Poth
0028: lat/lon GmbH
0029: Aennchenstr. 19
0030: 53177 Bonn
0031: Germany
0032: E-Mail: poth@lat-lon.de
0033:
0034: Prof. Dr. Klaus Greve
0035: Department of Geography
0036: University of Bonn
0037: Meckenheimer Allee 166
0038: 53115 Bonn
0039: Germany
0040: E-Mail: greve@giub.uni-bonn.de
0041:
0042:
0043: ---------------------------------------------------------------------------*/
0044: package org.deegree.ogcwebservices.wms;
0045:
0046: import java.awt.Color;
0047: import java.awt.image.BufferedImage;
0048: import java.io.ByteArrayInputStream;
0049: import java.io.InputStreamReader;
0050: import java.io.Reader;
0051: import java.io.StringReader;
0052: import java.net.URI;
0053: import java.net.URL;
0054: import java.util.ArrayList;
0055: import java.util.Arrays;
0056: import java.util.Iterator;
0057: import java.util.List;
0058: import java.util.Map;
0059: import java.util.concurrent.Callable;
0060: import java.util.concurrent.CancellationException;
0061:
0062: import org.deegree.datatypes.QualifiedName;
0063: import org.deegree.datatypes.Types;
0064: import org.deegree.datatypes.UnknownTypeException;
0065: import org.deegree.datatypes.values.Values;
0066: import org.deegree.framework.concurrent.ExecutionFinishedEvent;
0067: import org.deegree.framework.concurrent.ExecutionFinishedListener;
0068: import org.deegree.framework.concurrent.Executor;
0069: import org.deegree.framework.log.ILogger;
0070: import org.deegree.framework.log.LoggerFactory;
0071: import org.deegree.framework.util.CharsetUtils;
0072: import org.deegree.framework.util.IDGenerator;
0073: import org.deegree.framework.util.MapUtils;
0074: import org.deegree.framework.util.NetWorker;
0075: import org.deegree.framework.xml.NamespaceContext;
0076: import org.deegree.framework.xml.XMLFragment;
0077: import org.deegree.framework.xml.XMLTools;
0078: import org.deegree.framework.xml.XSLTDocument;
0079: import org.deegree.graphics.transformation.GeoTransform;
0080: import org.deegree.graphics.transformation.WorldToScreenTransform;
0081: import org.deegree.i18n.Messages;
0082: import org.deegree.model.coverage.grid.ImageGridCoverage;
0083: import org.deegree.model.crs.CRSFactory;
0084: import org.deegree.model.crs.CoordinateSystem;
0085: import org.deegree.model.crs.GeoTransformer;
0086: import org.deegree.model.feature.Feature;
0087: import org.deegree.model.feature.FeatureCollection;
0088: import org.deegree.model.feature.FeatureFactory;
0089: import org.deegree.model.feature.FeatureProperty;
0090: import org.deegree.model.feature.GMLFeatureCollectionDocument;
0091: import org.deegree.model.feature.schema.FeatureType;
0092: import org.deegree.model.feature.schema.PropertyType;
0093: import org.deegree.model.filterencoding.ComplexFilter;
0094: import org.deegree.model.filterencoding.FeatureFilter;
0095: import org.deegree.model.filterencoding.FeatureId;
0096: import org.deegree.model.filterencoding.Filter;
0097: import org.deegree.model.spatialschema.Envelope;
0098: import org.deegree.model.spatialschema.GMLGeometryAdapter;
0099: import org.deegree.model.spatialschema.Geometry;
0100: import org.deegree.model.spatialschema.GeometryFactory;
0101: import org.deegree.ogcbase.CommonNamespaces;
0102: import org.deegree.ogcbase.InvalidSRSException;
0103: import org.deegree.ogcbase.PropertyPath;
0104: import org.deegree.ogcwebservices.OGCWebService;
0105: import org.deegree.ogcwebservices.OGCWebServiceException;
0106: import org.deegree.ogcwebservices.OGCWebServiceRequest;
0107: import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
0108: import org.deegree.ogcwebservices.wfs.WFService;
0109: import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
0110: import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
0111: import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
0112: import org.deegree.ogcwebservices.wfs.operation.GetFeature;
0113: import org.deegree.ogcwebservices.wfs.operation.Query;
0114: import org.deegree.ogcwebservices.wms.capabilities.Layer;
0115: import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
0116: import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
0117: import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
0118: import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
0119: import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
0120: import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult;
0121: import org.deegree.ogcwebservices.wms.operation.GetMap;
0122: import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
0123: import org.deegree.processing.raster.converter.Image2RawData;
0124: import org.w3c.dom.Document;
0125:
0126: /**
0127: *
0128: *
0129: *
0130: * @version $Revision: 9697 $
0131: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
0132: * @author last edited by: $Author: aschmitz $
0133: *
0134: * @version 1.0. $Revision: 9697 $, $Date: 2008-01-23 03:19:46 -0800 (Wed, 23 Jan 2008) $
0135: *
0136: */
0137: class DefaultGetFeatureInfoHandler implements GetFeatureInfoHandler,
0138: ExecutionFinishedListener<Object[]> {
0139:
0140: protected static final ILogger LOG = LoggerFactory
0141: .getLogger(DefaultGetFeatureInfoHandler.class);
0142:
0143: private static final double DEFAULT_PIXEL_SIZE = 0.00028;
0144:
0145: protected GetFeatureInfo request = null;
0146:
0147: protected GetMap getMapRequest = null;
0148:
0149: protected WMSConfigurationType configuration = null;
0150:
0151: // collects the reponse for each layer
0152: private Object[] featCol = null;
0153:
0154: // scale of the map
0155: protected double scale = 0;
0156:
0157: // holds the number of request services that have responsed
0158: private int count = 0;
0159:
0160: // CRS of the request
0161: protected CoordinateSystem reqCRS = null;
0162:
0163: protected static final QualifiedName VALUE = new QualifiedName(
0164: "value");
0165:
0166: /**
0167: * Creates a new GetMapHandler object.
0168: *
0169: * @param capabilities
0170: * @param request
0171: * request to perform
0172: * @throws OGCWebServiceException
0173: */
0174: public DefaultGetFeatureInfoHandler(
0175: WMSConfigurationType capabilities, GetFeatureInfo request)
0176: throws OGCWebServiceException {
0177:
0178: this .request = request;
0179: this .configuration = capabilities;
0180: getMapRequest = request.getGetMapRequestCopy();
0181: try {
0182: reqCRS = CRSFactory.create(getMapRequest.getSrs());
0183: if (reqCRS == null) {
0184: throw new InvalidSRSException(Messages.getMessage(
0185: "WMS_UNKNOWN_CRS", getMapRequest.getSrs()));
0186: }
0187: } catch (Exception e) {
0188: throw new InvalidSRSException(Messages.getMessage(
0189: "WMS_UNKNOWN_CRS", getMapRequest.getSrs()));
0190: }
0191: try {
0192: Envelope bbox = getMapRequest.getBoundingBox();
0193: scale = MapUtils.calcScale(getMapRequest.getWidth(),
0194: getMapRequest.getHeight(), bbox, reqCRS,
0195: DEFAULT_PIXEL_SIZE);
0196: } catch (Exception e) {
0197: LOG.logDebug(e.getLocalizedMessage(), e);
0198: throw new OGCWebServiceException(Messages.getMessage(
0199: "WMS_SCALECALC", e));
0200: }
0201:
0202: }
0203:
0204: /**
0205: * increases the counter variable that holds the number of services that has sent a response.
0206: * All data are available if the counter value equals the number of requested layers.
0207: */
0208: protected synchronized void increaseCounter() {
0209: count++;
0210: }
0211:
0212: /**
0213: * performs a GetFeatureInfo request and retruns the result encapsulated within a
0214: * <tt>WMSFeatureInfoResponse</tt> object.
0215: * <p>
0216: * The method throws an WebServiceException that only shall be thrown if an fatal error occurs
0217: * that makes it imposible to return a result. If something wents wrong performing the request
0218: * (none fatal error) The exception shall be encapsulated within the response object to be
0219: * returned to the client as requested (GetFeatureInfo-Request EXCEPTION-Parameter).
0220: *
0221: * <p>
0222: * All sublayers of the queried layer will be added automatically. Non-queryable sublayers are
0223: * then ignored in the response.
0224: * </p>
0225: *
0226: * @return response to the GetFeatureInfo response
0227: */
0228: public GetFeatureInfoResult performGetFeatureInfo()
0229: throws OGCWebServiceException {
0230:
0231: String[] qlayers = request.getQueryLayers();
0232:
0233: List<Layer> allLayers = new ArrayList<Layer>();
0234:
0235: // here, the explicitly queried layers are checked for being queryable and known
0236: for (int i = 0; i < qlayers.length; i++) {
0237: Layer layer = configuration.getLayer(qlayers[i]);
0238:
0239: if (layer == null) {
0240: throw new LayerNotDefinedException(Messages.getMessage(
0241: "WMS_UNKNOWNLAYER", qlayers[i]));
0242: }
0243: if (!layer.isQueryable()) {
0244: throw new LayerNotQueryableException(Messages
0245: .getMessage("WMS_LAYER_NOT_QUERYABLE",
0246: qlayers[i]));
0247: }
0248: if (!layer.isSrsSupported(getMapRequest.getSrs())) {
0249: throw new InvalidSRSException(Messages.getMessage(
0250: "WMS_UNKNOWN_CRS_FOR_LAYER", getMapRequest
0251: .getSrs(), qlayers[i]));
0252: }
0253:
0254: allLayers.add(layer);
0255:
0256: // sublayers are added WITHOUT being checked for being queryable
0257: // This is desirable for example in the following scenario:
0258: // Suppose one queryable layer contains a lot of other layers,
0259: // that are mostly queryable. Then you can query all of those layers
0260: // at once by just querying the enclosing layer (the unqueryable
0261: // sublayers are ignored).
0262: allLayers.addAll(Arrays.asList(layer.getLayer()));
0263: }
0264:
0265: Layer[] layerList = allLayers.toArray(new Layer[allLayers
0266: .size()]);
0267:
0268: // there must be one feature collection for each requested layer
0269: int cnt = countNumberOfQueryableDataSources(layerList);
0270: featCol = new Object[cnt];
0271:
0272: // invokes the data supplyer for each layer in an independ thread
0273: int kk = 0;
0274: for (int i = 0; i < layerList.length; i++) {
0275: if (validate(layerList[i])) {
0276: AbstractDataSource datasource[] = layerList[i]
0277: .getDataSource();
0278: for (int j = 0; j < datasource.length; j++) {
0279: if (datasource[j].isQueryable()
0280: && isValidArea(datasource[j].getValidArea())) {
0281: ServiceInvoker si = new ServiceInvoker(
0282: layerList[i], datasource[j], kk++);
0283: ServiceInvokerTask task = new ServiceInvokerTask(
0284: si);
0285: Executor.getInstance().performAsynchronously(
0286: task, this );
0287: }
0288: }
0289: } else {
0290: // set feature collection to null if no data are available for the requested
0291: // area and/or scale. This will cause this index position will be ignored
0292: // when creating the final result
0293: featCol[kk++] = null;
0294: increaseCounter();
0295: }
0296: }
0297:
0298: // waits until the requested layers are available as <tt>DisplayElements</tt>
0299: // or the time limit has been reached.
0300: // TODO
0301: // substitue by an event based approach
0302: try {
0303: waitForFinish();
0304: } catch (Exception e) {
0305: return createExceptionResponse(e);
0306: }
0307:
0308: GetFeatureInfoResult res = createFeatureInfoResponse();
0309:
0310: return res;
0311: }
0312:
0313: /**
0314: * returns the number of datasources assigned to the queried layers that are queryable
0315: *
0316: * @param layerList
0317: * @return the number
0318: */
0319: private int countNumberOfQueryableDataSources(Layer[] layerList) {
0320: int cnt = 0;
0321: for (int i = 0; i < layerList.length; i++) {
0322: AbstractDataSource[] ds = layerList[i].getDataSource();
0323: for (int j = 0; j < ds.length; j++) {
0324: if (ds[j].isQueryable()
0325: && isValidArea(ds[j].getValidArea())) {
0326: cnt++;
0327: }
0328: }
0329: }
0330: return cnt;
0331: }
0332:
0333: /**
0334: * returns true if the requested boundingbox intersects with the valid area of a datasource
0335: *
0336: * @param validArea
0337: */
0338: private boolean isValidArea(Geometry validArea) {
0339:
0340: if (validArea != null) {
0341: try {
0342: Envelope env = request.getGetMapRequestCopy()
0343: .getBoundingBox();
0344: Geometry geom = GeometryFactory.createSurface(env,
0345: reqCRS);
0346: if (!reqCRS.getIdentifier()
0347: .equals(
0348: validArea.getCoordinateSystem()
0349: .getIdentifier())) {
0350: // if requested CRS is not identical to the CRS of the valid area
0351: // a transformation must be performed before intersection can
0352: // be checked
0353: GeoTransformer gt = new GeoTransformer(validArea
0354: .getCoordinateSystem());
0355: geom = gt.transform(geom);
0356: }
0357: return geom.intersects(validArea);
0358: } catch (Exception e) {
0359: // should never happen
0360: LOG.logError("Could not validate WMS datasource area",
0361: e);
0362: }
0363: }
0364: return true;
0365: }
0366:
0367: /**
0368: * validates if the requested layer matches the conditions of the request if not a
0369: * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't
0370: * able to deviever data for the requested area and/or scale false will be returned. If the
0371: * layer matches the request and contains data for the requested area and/or scale true will be
0372: * returned.
0373: *
0374: * @param layer
0375: * layer as defined at the capabilities/configuration
0376: */
0377: private boolean validate(Layer layer) throws OGCWebServiceException {
0378:
0379: // why the layer can be null here is not known, but just in case:
0380: String name = (layer == null) ? "" : layer.getName();
0381:
0382: // check for valid coordinated reference system
0383: String[] srs = layer.getSrs();
0384: boolean tmp = false;
0385: for (int i = 0; i < srs.length; i++) {
0386: if (srs[i].equalsIgnoreCase(getMapRequest.getSrs())) {
0387: tmp = true;
0388: break;
0389: }
0390: }
0391:
0392: if (!tmp) {
0393: throw new InvalidSRSException(Messages.getMessage(
0394: "WMS_INVALIDSRS", name, getMapRequest.getSrs()));
0395: }
0396:
0397: // check bounding box
0398: try {
0399:
0400: Envelope bbox = getMapRequest.getBoundingBox();
0401: Envelope layerBbox = layer.getLatLonBoundingBox();
0402: if (!getMapRequest.getSrs().equalsIgnoreCase("EPSG:4326")) {
0403: // transform the bounding box of the request to EPSG:4326
0404: GeoTransformer gt = new GeoTransformer(CRSFactory
0405: .create("EPSG:4326"));
0406: bbox = gt.transform(bbox, reqCRS);
0407: }
0408:
0409: if (!bbox.intersects(layerBbox)) {
0410: return false;
0411: }
0412:
0413: } catch (Exception e) {
0414: throw new OGCWebServiceException(Messages
0415: .getMessage("WMS_BBOXCOMPARSION"));
0416: }
0417:
0418: return true;
0419: }
0420:
0421: /**
0422: * creates a <tt>GetFeatureInfoResult</tt> containing an <tt>OGCWebServiceException</tt>
0423: *
0424: * @param e
0425: * exception to encapsulate into the response
0426: */
0427: private GetFeatureInfoResult createExceptionResponse(Exception e) {
0428:
0429: OGCWebServiceException exce = null;
0430:
0431: // default --> application/vnd.ogc.se_xml
0432: exce = new OGCWebServiceException(getClass().getName(), e
0433: .getMessage());
0434:
0435: GetFeatureInfoResult res = WMSProtocolFactory
0436: .createGetFeatureInfoResponse(request, exce, null);
0437:
0438: return res;
0439: }
0440:
0441: /**
0442: * waits until the requested layers are available as <tt>DisplayElements</tt> or the time
0443: * limit has been reached. If the waiting is terminated by reaching the time limit an
0444: * <tt>WebServiceException</tt> will be thrown to indicated that the request couldn't be
0445: * performed correctly.
0446: *
0447: * @throws WebServiceException
0448: * if the time limit has been reached
0449: */
0450: private void waitForFinish() throws OGCWebServiceException,
0451: Exception {
0452:
0453: // subtract 1 second for architecture overhead and image creation
0454: long timeLimit = 1000 * (configuration.getDeegreeParams()
0455: .getRequestTimeLimit() - 1);
0456: // long timeLimit = 1000 * 100;
0457: long runTime = 0;
0458:
0459: while (count < featCol.length) {
0460: try {
0461: Thread.sleep(100);
0462: } catch (Exception e) {
0463: LOG.logError("WMS_WAITING_LOOP", e);
0464: throw e;
0465: }
0466:
0467: runTime += 100;
0468:
0469: // finish loop after if request performing hasn't been completed
0470: // after the time limit is reached
0471: if (runTime > timeLimit) {
0472: throw new OGCWebServiceException(Messages
0473: .getMessage("WMS_TIMEOUT"));
0474: }
0475: }
0476:
0477: }
0478:
0479: /**
0480: * will be called each time a datasource has been read
0481: *
0482: * @param returnValue
0483: */
0484: public synchronized void executionFinished(
0485: ExecutionFinishedEvent<Object[]> returnValue) {
0486: Object[] o = null;
0487: try {
0488: o = returnValue.getResult();
0489: } catch (Throwable t) {
0490: LOG.logError("WMS_GETFEATURE_EXCEPTION", t);
0491: }
0492: featCol[((Integer) o[0]).intValue()] = o[1];
0493: increaseCounter();
0494: }
0495:
0496: /**
0497: * generates the desired output from the GMLs
0498: *
0499: * @return the result object
0500: * @throws OGCWebServiceException
0501: */
0502: private GetFeatureInfoResult createFeatureInfoResponse()
0503: throws OGCWebServiceException {
0504:
0505: Envelope bbox = getMapRequest.getBoundingBox();
0506:
0507: StringBuffer sb = new StringBuffer(20000);
0508: sb.append("<ll:FeatureCollection ");
0509:
0510: URL schemaLoc = configuration.getDeegreeParams()
0511: .getFeatureSchemaLocation();
0512: if (schemaLoc != null) {
0513: sb.append("xsi:schemaLocation='");
0514: sb.append(configuration.getDeegreeParams()
0515: .getFeatureSchemaNamespace());
0516: sb.append(" ");
0517: sb.append(schemaLoc.toExternalForm());
0518: sb.append("'");
0519: }
0520:
0521: sb.append(" xmlns:gml='http://www.opengis.net/gml' ");
0522: sb.append("xmlns:ll='http://www.lat-lon.de' ");
0523: sb
0524: .append("xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' ");
0525: URL url = configuration.getDeegreeParams().getSchemaLocation();
0526: if (url != null) {
0527: sb.append("xsi:schemaLocation='");
0528: sb.append("http://www.lat-lon.de "
0529: + NetWorker.url2String(url) + "'");
0530: }
0531: sb.append("><gml:boundedBy>");
0532: sb.append("<gml:Box srsName='" + getMapRequest.getSrs() + "'>");
0533: sb.append("<gml:coordinates>" + bbox.getMin().getX() + ",");
0534: sb.append(bbox.getMin().getY() + " " + bbox.getMax().getX()
0535: + ",");
0536: sb.append(bbox.getMax().getY() + "</gml:coordinates>");
0537: sb.append("</gml:Box></gml:boundedBy>");
0538:
0539: int cnt = 0;
0540:
0541: for (int i = 0; i < featCol.length; i++) {
0542: if (featCol[i] instanceof OGCWebServiceException) {
0543: throw (OGCWebServiceException) featCol[i];
0544: }
0545: if (featCol[i] != null) {
0546: FeatureCollection fc = (FeatureCollection) featCol[i];
0547: cnt = appendFeatureCollection(fc, sb, cnt);
0548: }
0549:
0550: // if ( cnt >= request.getFeatureCount() ) break;
0551: }
0552: sb.append("</ll:FeatureCollection>");
0553:
0554: GetFeatureInfoResult response = WMSProtocolFactory
0555: .createGetFeatureInfoResponse(request, null, sb
0556: .toString());
0557:
0558: return response;
0559: }
0560:
0561: /**
0562: *
0563: * @param col
0564: * @param sb
0565: * @param cnt
0566: * @return a counter, probably the same that is given as argument
0567: */
0568: private int appendFeatureCollection(FeatureCollection col,
0569: StringBuffer sb, int cnt) {
0570:
0571: Feature[] feat = col.toArray();
0572: if (feat != null) {
0573: for (int j = 0; j < feat.length; j++) {
0574: FeatureType ft = feat[j].getFeatureType();
0575: PropertyType[] ftp = ft.getProperties();
0576: cnt++;
0577: sb.append("<gml:featureMember>");
0578: sb.append("<ll:").append(ft.getName().getLocalName());
0579: sb.append(" fid='").append(
0580: feat[j].getId().replace(' ', '_')).append("'>");
0581: for (int i = 0; i < ftp.length; i++) {
0582: if (ftp[i].getType() != Types.GEOMETRY
0583: && ftp[i].getType() != Types.POINT
0584: && ftp[i].getType() != Types.CURVE
0585: && ftp[i].getType() != Types.SURFACE
0586: && ftp[i].getType() != Types.MULTIPOINT
0587: && ftp[i].getType() != Types.MULTICURVE
0588: && ftp[i].getType() != Types.MULTISURFACE) {
0589:
0590: FeatureProperty[] props = feat[j]
0591: .getProperties(ftp[i].getName());
0592: if (props != null) {
0593: for (FeatureProperty property : props) {
0594: Object value = property.getValue();
0595: sb.append("<ll:"
0596: + ftp[i].getName()
0597: .getLocalName() + ">");
0598: if (value instanceof FeatureCollection) {
0599: FeatureCollection fc = (FeatureCollection) value;
0600: appendFeatureCollection(fc, sb, cnt);
0601: } else {
0602: sb.append("<![CDATA[")
0603: .append(value)
0604: .append("]]>");
0605: }
0606: sb.append("</ll:"
0607: + ftp[i].getName()
0608: .getLocalName() + ">");
0609: }
0610: }
0611: }
0612: }
0613: sb.append("</ll:").append(ft.getName().getLocalName())
0614: .append('>');
0615: sb.append("</gml:featureMember>");
0616: if (cnt >= request.getFeatureCount())
0617: break;
0618: }
0619: }
0620:
0621: return cnt;
0622: }
0623:
0624: // //////////////////////////////////////////////////////////////////////////
0625: // inner classes //
0626: // //////////////////////////////////////////////////////////////////////////
0627:
0628: /**
0629: * Inner class for accessing the data of one layer and creating a GML document from it. The
0630: * class extends <tt>Thread</tt> and implements the run method, so that a parallel data
0631: * accessing from several layers is possible.
0632: *
0633: * @version $Revision: 9697 $
0634: * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
0635: */
0636: public class ServiceInvoker {
0637: private Layer layer = null;
0638:
0639: private int index = 0;
0640:
0641: private AbstractDataSource datasource = null;
0642:
0643: /**
0644: * Creates a new ServiceInvoker object.
0645: *
0646: * @param layer
0647: * @param datasource
0648: * @param index
0649: * index of the requested layer
0650: */
0651: ServiceInvoker(Layer layer, AbstractDataSource datasource,
0652: int index) {
0653: this .layer = layer;
0654: this .index = index;
0655: this .datasource = datasource;
0656: }
0657:
0658: /**
0659: * central method for access the data assigned to a datasource
0660: *
0661: * @return result of feature info query
0662: */
0663: public Object run() {
0664: Object response = null;
0665: if (datasource != null) {
0666: OGCWebServiceRequest request = null;
0667: try {
0668: int type = datasource.getType();
0669: switch (type) {
0670: case AbstractDataSource.LOCALWFS:
0671: case AbstractDataSource.REMOTEWFS: {
0672: request = createGetFeatureRequest((LocalWFSDataSource) datasource);
0673: break;
0674: }
0675: case AbstractDataSource.LOCALWCS:
0676: case AbstractDataSource.REMOTEWCS: {
0677: request = GetMapServiceInvokerForNL
0678: .createGetCoverageRequest(datasource,
0679: getMapRequest);
0680: break;
0681: }
0682: case AbstractDataSource.REMOTEWMS: {
0683: request = createGetFeatureInfo(datasource);
0684: break;
0685: }
0686: }
0687: } catch (Exception e) {
0688: OGCWebServiceException exce = new OGCWebServiceException(
0689: "ServiceInvoker: " + layer.getName(),
0690: Messages.getMessage("WMS_CREATE_QUERY"));
0691: response = new Object[] { new Integer(index), exce };
0692: LOG.logError(Messages
0693: .getMessage("WMS_CREATE_QUERY")
0694: + ": " + e.getMessage(), e);
0695: throw new RuntimeException(e);
0696: }
0697:
0698: try {
0699: Executor executor = Executor.getInstance();
0700: DoServiceTask task = new DoServiceTask(datasource
0701: .getOGCWebService(), request);
0702: Object o = executor.performSynchronously(task,
0703: datasource.getRequestTimeLimit() * 1000);
0704: response = handleResponse(o);
0705: } catch (CancellationException e) {
0706: // exception can't be re-thrown because responsible GetMapHandler
0707: // must collect all responses of all datasources
0708: String s = Messages.getMessage(
0709: "WMS_TIMEOUTDATASOURCE", new Integer(
0710: datasource.getRequestTimeLimit()));
0711: LOG.logError(s, e);
0712: if (datasource.isFailOnException()) {
0713: OGCWebServiceException exce = new OGCWebServiceException(
0714: getClass().getName(), s);
0715: response = new Object[] { new Integer(index),
0716: exce };
0717: } else {
0718: response = new Object[] { new Integer(index),
0719: null };
0720: }
0721: } catch (Throwable t) {
0722: // exception can't be re-thrown because responsible GetMapHandler
0723: // must collect all responses of all datasources
0724: String s = Messages.getMessage(
0725: "WMS_ERRORDOSERVICE", t.getMessage());
0726: LOG.logError(s, t);
0727: if (datasource.isFailOnException()) {
0728: OGCWebServiceException exce = new OGCWebServiceException(
0729: getClass().getName(), s);
0730: response = new Object[] { new Integer(index),
0731: exce };
0732: } else {
0733: response = new Object[] { new Integer(index),
0734: null };
0735: }
0736: }
0737:
0738: }
0739:
0740: return response;
0741:
0742: }
0743:
0744: /**
0745: * creates a getFeature request considering the getMap request and the filterconditions
0746: * defined in the submitted <tt>DataSource</tt> object. The request will be encapsualted
0747: * within a <tt>OGCWebServiceEvent</tt>.
0748: *
0749: * @param ds
0750: * @return GetFeature request object
0751: */
0752: private GetFeature createGetFeatureRequest(LocalWFSDataSource ds)
0753: throws Exception {
0754:
0755: Envelope targetArea = calcTargetArea(ds);
0756:
0757: // no filter condition has been defined
0758: StringBuffer sb = new StringBuffer(2000);
0759: sb.append("<?xml version='1.0' encoding='"
0760: + CharsetUtils.getSystemCharset() + "'?>");
0761: sb
0762: .append("<GetFeature xmlns='http://www.opengis.net/wfs' ");
0763: sb.append("xmlns:ogc='http://www.opengis.net/ogc' ");
0764: sb.append("xmlns:wfs='http://www.opengis.net/wfs' ");
0765: sb.append("xmlns:gml='http://www.opengis.net/gml' ");
0766: sb.append("xmlns:").append(ds.getName().getPrefix())
0767: .append('=');
0768: sb.append("'").append(ds.getName().getNamespace()).append(
0769: "' ");
0770: sb.append("service='WFS' version='1.1.0' ");
0771: sb.append("outputFormat='FEATURECOLLECTION'>");
0772: sb.append("<Query typeName='"
0773: + ds.getName().getPrefixedName() + "'>");
0774:
0775: Query query = ds.getQuery();
0776:
0777: // append <wfs:PropertyName> elements
0778: if (query != null && query.getPropertyNames() != null) {
0779: PropertyPath[] propertyNames = query.getPropertyNames();
0780: for (PropertyPath path : propertyNames) {
0781: NamespaceContext nsContext = path
0782: .getNamespaceContext();
0783: sb.append("<wfs:PropertyName");
0784: Map<String, URI> namespaceMap = nsContext
0785: .getNamespaceMap();
0786: Iterator<String> prefixIter = namespaceMap.keySet()
0787: .iterator();
0788: while (prefixIter.hasNext()) {
0789: String prefix = prefixIter.next();
0790: if (!CommonNamespaces.XMLNS_PREFIX
0791: .equals(prefix)) {
0792: URI namespace = namespaceMap.get(prefix);
0793: sb.append(" xmlns:" + prefix + "=\""
0794: + namespace + "\"");
0795: }
0796: }
0797: sb.append('>');
0798: sb.append(path.getAsString());
0799: sb.append("</wfs:PropertyName>");
0800: }
0801: }
0802:
0803: sb.append("<ogc:Filter>");
0804: if (query == null) {
0805: // BBOX operation for speeding up the search at simple datasources
0806: // like shapes
0807: sb.append("<ogc:BBOX><PropertyName>");
0808: sb.append(ds.getGeometryProperty().getPrefixedName());
0809: sb.append("</PropertyName>");
0810: sb.append(GMLGeometryAdapter.exportAsBox(targetArea));
0811: sb.append("</ogc:BBOX>");
0812: sb.append("</ogc:Filter></Query></GetFeature>");
0813: } else {
0814: Filter filter = query.getFilter();
0815:
0816: sb.append("<ogc:And>");
0817: // BBOX operation for speeding up the search at simple datasources
0818: // like shapes
0819: sb.append("<ogc:BBOX><PropertyName>"
0820: + ds.getGeometryProperty().getPrefixedName());
0821: sb.append("</PropertyName>");
0822: sb.append(GMLGeometryAdapter.exportAsBox(targetArea));
0823: sb.append("</ogc:BBOX>");
0824:
0825: if (filter instanceof ComplexFilter) {
0826: org.deegree.model.filterencoding.Operation op = ((ComplexFilter) filter)
0827: .getOperation();
0828: sb.append(op.toXML());
0829: } else {
0830: ArrayList<FeatureId> featureIds = ((FeatureFilter) filter)
0831: .getFeatureIds();
0832: for (int i = 0; i < featureIds.size(); i++) {
0833: FeatureId fid = featureIds.get(i);
0834: sb.append(fid.toXML());
0835: }
0836: }
0837: sb
0838: .append("</ogc:And></ogc:Filter></Query></GetFeature>");
0839: }
0840:
0841: if (LOG.getLevel() == ILogger.LOG_DEBUG) {
0842: LOG.logDebug("GetFeature-request:\n" + sb);
0843: }
0844:
0845: // create dom representation of the request
0846: StringReader sr = new StringReader(sb.toString());
0847: Document doc = XMLTools.parse(sr);
0848:
0849: // create OGCWebServiceEvent object
0850: IDGenerator idg = IDGenerator.getInstance();
0851: GetFeature gfr = GetFeature.create(""
0852: + idg.generateUniqueID(), doc.getDocumentElement());
0853:
0854: return gfr;
0855: }
0856:
0857: /**
0858: * calculates the target area for the getfeatureinfo request from the maps bounding box, the
0859: * its size and the image coordinates of interest. An area is calculated instead of using a
0860: * point because to consider uncertainties determining the point of interest
0861: *
0862: * @param ds
0863: * <tt>DataSource</tt> of the layer that is requested for feature infos (each
0864: * layer may be offered in its own crs)
0865: */
0866: private Envelope calcTargetArea(AbstractDataSource ds)
0867: throws OGCWebServiceException {
0868:
0869: int width = request.getGetMapRequestCopy().getWidth();
0870: int height = request.getGetMapRequestCopy().getHeight();
0871: int x = request.getClickPoint().x;
0872: int y = request.getClickPoint().y;
0873:
0874: Envelope bbox = request.getGetMapRequestCopy()
0875: .getBoundingBox();
0876:
0877: // transform request bounding box to the coordinate reference
0878: // system the WFS holds the data if requesting CRS and WFS-Data
0879: // crs are different
0880: // WFService se = (WFService)ds.getOGCWebService();
0881: // WFSCapabilities capa = (WFSCapabilities)se.getWFSCapabilities();
0882: //
0883: // org.deegree.ogcwebservices.wfs.capabilities.FeatureType ft =
0884: // capa.getFeatureTypeList().getFeatureType( ds.getName() );
0885: WFService se = (WFService) ds.getOGCWebService();
0886: WFSCapabilities capa = se.getCapabilities();
0887: QualifiedName gn = ds.getName();
0888: WFSFeatureType ft = capa.getFeatureTypeList()
0889: .getFeatureType(gn);
0890:
0891: if (ft == null) {
0892: throw new OGCWebServiceException(Messages.getMessage(
0893: "WMS_UNKNOWNFT", ds.getName()));
0894: }
0895: String crs = ft.getDefaultSRS().toASCIIString();
0896: Envelope tBbox = null;
0897: try {
0898: GeoTransform gt = new WorldToScreenTransform(bbox
0899: .getMin().getX(), bbox.getMin().getY(), bbox
0900: .getMax().getX(), bbox.getMax().getY(), 0, 0,
0901: width - 1, height - 1);
0902: double[] target = new double[4];
0903: int rad = configuration.getDeegreeParams()
0904: .getFeatureInfoRadius();
0905: target[0] = gt.getSourceX(x - rad);
0906: target[1] = gt.getSourceY(y + rad);
0907: target[2] = gt.getSourceX(x + rad);
0908: target[3] = gt.getSourceY(y - rad);
0909:
0910: tBbox = GeometryFactory.createEnvelope(target[0],
0911: target[1], target[2], target[3], null);
0912: if (!(crs.equalsIgnoreCase(request
0913: .getGetMapRequestCopy().getSrs()))) {
0914: GeoTransformer transformer = new GeoTransformer(
0915: CRSFactory.create(crs));
0916: tBbox = transformer.transform(tBbox, reqCRS);
0917: }
0918:
0919: } catch (Exception e) {
0920: throw new OGCWebServiceException(e.toString());
0921: }
0922:
0923: return tBbox;
0924: }
0925:
0926: /**
0927: * creates a GetFeatureInfo request for requesting a cascaded remote WMS The request will be
0928: * encapsualted within a <tt>OGCWebServiceEvent</tt>.
0929: *
0930: * @param ds
0931: * @return GetFeatureInfo request object
0932: */
0933: private GetFeatureInfo createGetFeatureInfo(
0934: AbstractDataSource ds) {
0935:
0936: // create embbeded map request
0937: GetMap gmr = ((RemoteWMSDataSource) ds).getGetMapRequest();
0938:
0939: String format = getMapRequest.getFormat();
0940:
0941: if (gmr != null && !"%default%".equals(gmr.getFormat())) {
0942: format = gmr.getFormat();
0943: }
0944:
0945: org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] lys = null;
0946: lys = new org.deegree.ogcwebservices.wms.operation.GetMap.Layer[1];
0947: lys[0] = GetMap.createLayer(layer.getName(), "$DEFAULT");
0948:
0949: if (gmr != null && gmr.getLayers() != null) {
0950: lys = gmr.getLayers();
0951: }
0952: Color bgColor = getMapRequest.getBGColor();
0953: if (gmr != null && gmr.getBGColor() != null) {
0954: bgColor = gmr.getBGColor();
0955: }
0956: Values time = getMapRequest.getTime();
0957: if (gmr != null && gmr.getTime() != null) {
0958: time = gmr.getTime();
0959: }
0960: Map<String, String> vendorSpecificParameter = getMapRequest
0961: .getVendorSpecificParameters();
0962: if (gmr != null
0963: && gmr.getVendorSpecificParameters() != null
0964: && gmr.getVendorSpecificParameters().size() > 0) {
0965: vendorSpecificParameter = gmr
0966: .getVendorSpecificParameters();
0967: }
0968: String version = "1.1.0";
0969: if (gmr != null && gmr.getVersion() != null) {
0970: version = gmr.getFormat();
0971: }
0972: Values elevation = getMapRequest.getElevation();
0973: if (gmr != null && gmr.getElevation() != null) {
0974: elevation = gmr.getElevation();
0975: }
0976: Map<String, Values> sampleDim = null;
0977: if (gmr != null && gmr.getSampleDimension() != null) {
0978: sampleDim = gmr.getSampleDimension();
0979: }
0980:
0981: IDGenerator idg = IDGenerator.getInstance();
0982: gmr = GetMap.create(version, "" + idg.generateUniqueID(),
0983: lys, elevation, sampleDim, format, getMapRequest
0984: .getWidth(), getMapRequest.getHeight(),
0985: getMapRequest.getSrs(), getMapRequest
0986: .getBoundingBox(), getMapRequest
0987: .getTransparency(), bgColor, getMapRequest
0988: .getExceptions(), time, null, null,
0989: vendorSpecificParameter);
0990:
0991: // create GetFeatureInfo request for cascaded/remote WMS
0992: String[] queryLayers = new String[] { ds.getName()
0993: .getPrefixedName() };
0994: GetFeatureInfo req = GetFeatureInfo.create("1.1.0", this
0995: .toString(), queryLayers, gmr,
0996: "application/vnd.ogc.gml", request
0997: .getFeatureCount(),
0998: request.getClickPoint(), request.getExceptions(),
0999: null, request.getVendorSpecificParameters());
1000:
1001: try {
1002: LOG.logDebug("cascaded GetFeatureInfo request: ", req
1003: .getRequestParameter());
1004: } catch (OGCWebServiceException e) {
1005: LOG.logError(e.getMessage(), e);
1006: }
1007:
1008: return req;
1009: }
1010:
1011: /**
1012: * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS
1013: * implementation accessed by this class is able to return the result of a request by
1014: * calling the write-method.
1015: *
1016: * @param result
1017: * to a GetXXX request
1018: * @return the response object
1019: * @throws Exception
1020: */
1021: private Object handleResponse(Object result) throws Exception {
1022: Object[] response = null;
1023: if (result instanceof FeatureResult) {
1024: response = handleGetFeatureResponse((FeatureResult) result);
1025: } else if (result instanceof ResultCoverage) {
1026: response = handleGetCoverageResponse((ResultCoverage) result);
1027: } else if (result instanceof GetFeatureInfoResult) {
1028:
1029: response = handleGetFeatureInfoResult((GetFeatureInfoResult) result);
1030: } else {
1031: throw new Exception(Messages
1032: .getMessage("WMS_UNKNOWNRESPONSEFORMAT"));
1033: }
1034: return response;
1035: }
1036:
1037: /**
1038: * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and
1039: * a <tt>Theme</tt> from it
1040: *
1041: * @param response
1042: * @return the response objects
1043: * @throws Exception
1044: */
1045: private Object[] handleGetFeatureResponse(FeatureResult response)
1046: throws Exception {
1047: FeatureCollection fc = null;
1048:
1049: Object o = response.getResponse();
1050: if (o instanceof FeatureCollection) {
1051: fc = (FeatureCollection) o;
1052: } else if (o.getClass() == byte[].class) {
1053: Reader reader = new InputStreamReader(
1054: new ByteArrayInputStream((byte[]) o));
1055: GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
1056: doc.load(reader, XMLFragment.DEFAULT_URL);
1057: fc = doc.parse();
1058: } else {
1059: throw new Exception(Messages
1060: .getMessage("WMS_UNKNOWNDATAFORMATFT"));
1061: }
1062: Object[] ro = new Object[2];
1063: ro[0] = new Integer(index);
1064: ro[1] = fc;
1065: return ro;
1066: }
1067:
1068: /**
1069: *
1070: * @param res
1071: */
1072: private Object[] handleGetFeatureInfoResult(
1073: GetFeatureInfoResult res) throws Exception {
1074:
1075: FeatureCollection fc = null;
1076: StringReader sr = new StringReader(res.getFeatureInfo());
1077: XMLFragment xml = new XMLFragment(sr,
1078: XMLFragment.DEFAULT_URL);
1079:
1080: if (LOG.getLevel() == ILogger.LOG_DEBUG) {
1081: LOG
1082: .logDebug("GetFeature-response (before transformation): "
1083: + xml.getAsPrettyString());
1084: }
1085:
1086: URL url = ((RemoteWMSDataSource) datasource)
1087: .getFeatureInfoTransform();
1088: if (url != null) {
1089: // transform incoming GML/XML to a GML application schema
1090: // that is understood by deegree
1091: XSLTDocument xslt = new XSLTDocument();
1092: xslt.load(url);
1093: xml = xslt.transform(xml, null, null, null);
1094: }
1095: GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
1096: doc.setRootElement(xml.getRootElement());
1097: fc = doc.parse();
1098: Object[] ro = new Object[2];
1099: ro[0] = new Integer(index);
1100: ro[1] = fc;
1101: return ro;
1102: }
1103:
1104: private Object[] handleGetCoverageResponse(
1105: ResultCoverage response) throws OGCWebServiceException {
1106: ImageGridCoverage gc = (ImageGridCoverage) response
1107: .getCoverage();
1108: Object[] result = new Object[2];
1109: if (gc != null) {
1110: BufferedImage bi = gc.getAsImage(-1, -1);
1111:
1112: float[][] data = new Image2RawData(bi).parse();
1113:
1114: double scaleX = (double) data[0].length
1115: / (double) getMapRequest.getWidth();
1116: double scaleY = (double) data.length
1117: / (double) getMapRequest.getHeight();
1118: int pxSizeX = (int) Math.round(scaleX);
1119: int pxSizeY = (int) Math.round(scaleY);
1120: if (pxSizeX == 0) {
1121: pxSizeX = 1;
1122: }
1123: if (pxSizeY == 0) {
1124: pxSizeY = 1;
1125: }
1126:
1127: LOG.logDebug("Size of grid coverage is "
1128: + data[0].length + "x" + data.length + ".");
1129: LOG.logDebug("Returning an area of " + pxSizeX + "x"
1130: + pxSizeY + " pixels.");
1131:
1132: int ix = (int) (request.getClickPoint().x * scaleX)
1133: - pxSizeX / 2;
1134: int iy = (int) (request.getClickPoint().y * scaleY)
1135: - pxSizeY / 2;
1136:
1137: // some checks to avoid areas that are not requestable
1138: if (ix < 0) {
1139: ix = 0;
1140: }
1141: if (iy < 0) {
1142: iy = 0;
1143: }
1144: if (ix >= (data[0].length - pxSizeX)) {
1145: ix = data[0].length - pxSizeX - 1;
1146: }
1147: if (iy >= (data.length - pxSizeY)) {
1148: iy = data.length - pxSizeY - 1;
1149: }
1150:
1151: FeatureCollection fc = FeatureFactory
1152: .createFeatureCollection(gc
1153: .getCoverageOffering().getName(),
1154: pxSizeX * pxSizeY);
1155:
1156: PropertyType pt = null;
1157: try {
1158: pt = FeatureFactory.createPropertyType(VALUE,
1159: new QualifiedName("xsd", "double",
1160: CommonNamespaces.XSNS), 1, 1);
1161: } catch (UnknownTypeException e) {
1162: LOG
1163: .logError(
1164: "The xsd:double type is not known?!? Get a new deegree.jar!",
1165: e);
1166: }
1167: FeatureType ft = FeatureFactory.createFeatureType(gc
1168: .getCoverageOffering().getName(), false,
1169: new PropertyType[] { pt });
1170:
1171: for (int x = ix; x < ix + pxSizeX; ++x) {
1172: for (int y = iy; y < iy + pxSizeY; ++y) {
1173: FeatureProperty p = FeatureFactory
1174: .createFeatureProperty(VALUE,
1175: new Double(data[y][x]));
1176: Feature f = FeatureFactory.createFeature(
1177: "ID_faked_for_" + x + "x" + y, ft,
1178: new FeatureProperty[] { p });
1179: fc.add(f);
1180: }
1181: }
1182:
1183: result[1] = fc;
1184: } else {
1185: throw new OGCWebServiceException(getClass().getName(),
1186: Messages.getMessage("WMS_NOCOVERAGE",
1187: datasource.getName()));
1188: }
1189:
1190: result[0] = new Integer(index);
1191: return result;
1192: }
1193:
1194: }
1195:
1196: private class DoServiceTask implements Callable<Object> {
1197:
1198: OGCWebService webService;
1199:
1200: OGCWebServiceRequest request;
1201:
1202: DoServiceTask(OGCWebService webService,
1203: OGCWebServiceRequest request) {
1204: this .webService = webService;
1205: this .request = request;
1206: }
1207:
1208: public Object call() throws Exception {
1209: return this .webService.doService(request);
1210: }
1211: }
1212:
1213: private class ServiceInvokerTask implements Callable<Object[]> {
1214:
1215: ServiceInvoker invoker;
1216:
1217: ServiceInvokerTask(ServiceInvoker invoker) {
1218: this .invoker = invoker;
1219: }
1220:
1221: public Object[] call() throws Exception {
1222: return (Object[]) this.invoker.run();
1223: }
1224: }
1225: }
|