001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/DefaultGetFeatureInfoHandler.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;
037
038 import static java.lang.System.currentTimeMillis;
039 import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetFeatureInfoResponse;
040
041 import java.awt.Color;
042 import java.awt.image.BufferedImage;
043 import java.io.BufferedReader;
044 import java.io.ByteArrayInputStream;
045 import java.io.IOException;
046 import java.io.InputStream;
047 import java.io.InputStreamReader;
048 import java.io.Reader;
049 import java.io.StringReader;
050 import java.net.URI;
051 import java.net.URL;
052 import java.util.ArrayList;
053 import java.util.Arrays;
054 import java.util.Iterator;
055 import java.util.LinkedList;
056 import java.util.List;
057 import java.util.Map;
058 import java.util.concurrent.Callable;
059 import java.util.concurrent.CancellationException;
060
061 import org.deegree.datatypes.QualifiedName;
062 import org.deegree.datatypes.Types;
063 import org.deegree.datatypes.UnknownTypeException;
064 import org.deegree.datatypes.values.Values;
065 import org.deegree.framework.concurrent.DoDatabaseQueryTask;
066 import org.deegree.framework.concurrent.DoExternalAccessTask;
067 import org.deegree.framework.concurrent.DoServiceTask;
068 import org.deegree.framework.concurrent.ExecutionFinishedEvent;
069 import org.deegree.framework.concurrent.Executor;
070 import org.deegree.framework.log.ILogger;
071 import org.deegree.framework.log.LoggerFactory;
072 import org.deegree.framework.util.CharsetUtils;
073 import org.deegree.framework.util.IDGenerator;
074 import org.deegree.framework.util.MapUtils;
075 import org.deegree.framework.xml.NamespaceContext;
076 import org.deegree.framework.xml.XMLFragment;
077 import org.deegree.framework.xml.XMLTools;
078 import org.deegree.framework.xml.XSLTDocument;
079 import org.deegree.graphics.transformation.GeoTransform;
080 import org.deegree.graphics.transformation.WorldToScreenTransform;
081 import org.deegree.i18n.Messages;
082 import org.deegree.model.coverage.grid.ImageGridCoverage;
083 import org.deegree.model.crs.CRSFactory;
084 import org.deegree.model.crs.CoordinateSystem;
085 import org.deegree.model.crs.GeoTransformer;
086 import org.deegree.model.feature.Feature;
087 import org.deegree.model.feature.FeatureCollection;
088 import org.deegree.model.feature.FeatureFactory;
089 import org.deegree.model.feature.FeatureProperty;
090 import org.deegree.model.feature.GMLFeatureCollectionDocument;
091 import org.deegree.model.feature.schema.FeatureType;
092 import org.deegree.model.feature.schema.PropertyType;
093 import org.deegree.model.filterencoding.ComplexFilter;
094 import org.deegree.model.filterencoding.FeatureFilter;
095 import org.deegree.model.filterencoding.FeatureId;
096 import org.deegree.model.filterencoding.Filter;
097 import org.deegree.model.spatialschema.Envelope;
098 import org.deegree.model.spatialschema.GMLGeometryAdapter;
099 import org.deegree.model.spatialschema.Geometry;
100 import org.deegree.model.spatialschema.GeometryFactory;
101 import org.deegree.ogcbase.CommonNamespaces;
102 import org.deegree.ogcbase.InvalidSRSException;
103 import org.deegree.ogcbase.PropertyPath;
104 import org.deegree.ogcwebservices.OGCWebServiceException;
105 import org.deegree.ogcwebservices.OGCWebServiceRequest;
106 import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
107 import org.deegree.ogcwebservices.wfs.RemoteWFService;
108 import org.deegree.ogcwebservices.wfs.WFService;
109 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
110 import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
111 import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
112 import org.deegree.ogcwebservices.wfs.operation.GetFeature;
113 import org.deegree.ogcwebservices.wfs.operation.Query;
114 import org.deegree.ogcwebservices.wms.capabilities.Dimension;
115 import org.deegree.ogcwebservices.wms.capabilities.Layer;
116 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
117 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
118 import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
119 import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource;
120 import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
121 import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
122 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
123 import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess;
124 import org.deegree.ogcwebservices.wms.operation.DimensionValues;
125 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
126 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult;
127 import org.deegree.ogcwebservices.wms.operation.GetMap;
128 import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
129 import org.deegree.processing.raster.converter.Image2RawData;
130 import org.w3c.dom.Document;
131
132 /**
133 *
134 *
135 *
136 * @version $Revision: 24806 $
137 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
138 * @author last edited by: $Author: apoth $
139 *
140 * @version 1.0. $Revision: 24806 $, $Date: 2010-06-09 16:41:16 +0200 (Mi, 09 Jun 2010) $
141 *
142 */
143 class DefaultGetFeatureInfoHandler implements GetFeatureInfoHandler {
144
145 protected static final ILogger LOG = LoggerFactory.getLogger( DefaultGetFeatureInfoHandler.class );
146
147 private static final double DEFAULT_PIXEL_SIZE = 0.00028;
148
149 protected GetFeatureInfo request = null;
150
151 protected GetMap getMapRequest = null;
152
153 protected WMSConfigurationType configuration = null;
154
155 // collects the reponse for each layer
156 private Object[] featCol = null;
157
158 // scale of the map
159 protected double scale = 0;
160
161 // CRS of the request
162 protected CoordinateSystem reqCRS = null;
163
164 protected static final QualifiedName VALUE = new QualifiedName( "value" );
165
166 /**
167 * Creates a new GetMapHandler object.
168 *
169 * @param capabilities
170 * @param request
171 * request to perform
172 * @throws OGCWebServiceException
173 */
174 public DefaultGetFeatureInfoHandler( WMSConfigurationType capabilities, GetFeatureInfo request )
175 throws OGCWebServiceException {
176
177 this.request = request;
178 this.configuration = capabilities;
179 getMapRequest = request.getGetMapRequestCopy();
180 try {
181 reqCRS = CRSFactory.create( getMapRequest.getSrs() );
182 if ( reqCRS == null ) {
183 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) );
184 }
185 } catch ( Exception e ) {
186 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) );
187 }
188 try {
189 Envelope bbox = getMapRequest.getBoundingBox();
190 double pixelSize = 1;
191 if ( request.getVersion().equals( "1.3.0" ) ) {
192 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
193 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
194 pixelSize = DEFAULT_PIXEL_SIZE;
195 }
196 scale = MapUtils.calcScale( getMapRequest.getWidth(), getMapRequest.getHeight(), bbox, reqCRS, pixelSize );
197 } catch ( Exception e ) {
198 LOG.logDebug( e.getLocalizedMessage(), e );
199 throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC", e ) );
200 }
201
202 }
203
204 /**
205 * performs a GetFeatureInfo request and retruns the result encapsulated within a <tt>WMSFeatureInfoResponse</tt>
206 * object.
207 * <p>
208 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
209 * imposible to return a result. If something wents wrong performing the request (none fatal error) The exception
210 * shall be encapsulated within the response object to be returned to the client as requested
211 * (GetFeatureInfo-Request EXCEPTION-Parameter).
212 *
213 * <p>
214 * All sublayers of the queried layer will be added automatically. Non-queryable sublayers are then ignored in the
215 * response.
216 * </p>
217 *
218 * @return response to the GetFeatureInfo response
219 */
220 public GetFeatureInfoResult performGetFeatureInfo()
221 throws OGCWebServiceException {
222 long runningTime = currentTimeMillis();
223
224 String[] qlayers = request.getQueryLayers();
225
226 List<Layer> allLayers = new ArrayList<Layer>();
227
228 // here, the explicitly queried layers are checked for being queryable and known
229 for ( int i = 0; i < qlayers.length; i++ ) {
230 Layer layer = configuration.getLayer( qlayers[i] );
231
232 if ( layer == null ) {
233 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", qlayers[i] ) );
234 }
235 if ( !layer.isQueryable() ) {
236 throw new LayerNotQueryableException( Messages.getMessage( "WMS_LAYER_NOT_QUERYABLE", qlayers[i] ) );
237 }
238 if ( !layer.isSrsSupported( getMapRequest.getSrs() ) ) {
239 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER",
240 getMapRequest.getSrs(), qlayers[i] ) );
241 }
242
243 allLayers.add( layer );
244
245 // sub layers are added WITHOUT being checked for being queryable
246 // This is desirable for example in the following scenario:
247 // Suppose one queryable layer contains a lot of other layers,
248 // that are mostly queryable. Then you can query all of those layers
249 // at once by just querying the enclosing layer (the unqueryable
250 // sub layers are ignored).
251 allLayers.addAll( Arrays.asList( layer.getLayer() ) );
252 }
253
254 Layer[] layerList = allLayers.toArray( new Layer[allLayers.size()] );
255
256 if ( layerList.length == 1 && layerList[0].getDataSource().length == 1
257 && layerList[0].getDataSource()[0].getFeatureInfoURL() != null ) {
258 LOG.logDebug( "GetFeatureInfo predefined response workaround enabled." );
259 try {
260 InputStream stream = layerList[0].getDataSource()[0].getFeatureInfoURL().openStream();
261 BufferedReader in = new BufferedReader( new InputStreamReader( stream ) );
262 StringBuilder sb = new StringBuilder();
263
264 while ( in.ready() ) {
265 sb.append( in.readLine() ).append( "\n" );
266 }
267
268 in.close();
269
270 return createGetFeatureInfoResponse( request, null, sb.toString() );
271 } catch ( IOException e ) {
272 LOG.logError( "Predefined GetFeatureInfo response could not be read/found.", e );
273 }
274 }
275
276 LinkedList<Callable<Object>> tasks = new LinkedList<Callable<Object>>();
277 for ( int i = 0; i < layerList.length; i++ ) {
278 if ( validate( layerList[i] ) ) {
279 AbstractDataSource datasource[] = layerList[i].getDataSource();
280 for ( int j = 0; j < datasource.length; j++ ) {
281 ScaleHint hint = datasource[j].getScaleHint();
282 if ( datasource[j].isQueryable() && isValidArea( datasource[j].getValidArea() )
283 && scale >= hint.getMin() && scale < hint.getMax() ) {
284 ServiceInvoker si = new ServiceInvoker( layerList[i], datasource[j] );
285 tasks.add( si );
286 } else {
287 LOG.logDebug( "Not using a datasource of layer " + layerList[i].getName()
288 + " due to scale or bbox limits." );
289 }
290 }
291 } else {
292 LOG.logDebug( "Layer " + layerList[i].getName() + " not validated." );
293 }
294 }
295
296 // was: subtract 1 second for architecture overhead and image creation
297 // am not going to do this!
298 long timeLimit = 1000 * ( configuration.getDeegreeParams().getRequestTimeLimit() );
299 timeLimit -= ( runningTime - currentTimeMillis() );
300 try {
301 List<ExecutionFinishedEvent<Object>> list = Executor.getInstance().performSynchronously( tasks, timeLimit );
302 featCol = new Object[list.size()];
303 int i = 0;
304 for ( ExecutionFinishedEvent<Object> evt : list ) {
305 try {
306 Object o = evt.getResult();
307 featCol[i++] = o;
308 } catch ( CancellationException e ) {
309 createExceptionResponse( e );
310 } catch ( Exception e ) {
311 createExceptionResponse( e );
312 } catch ( Throwable e ) {
313 LOG.logError( "Unknown error", e );
314 }
315 }
316 } catch ( InterruptedException e ) {
317 createExceptionResponse( e );
318 }
319
320 GetFeatureInfoResult res = createFeatureInfoResponse();
321
322 return res;
323 }
324
325 /**
326 * returns true if the requested boundingbox intersects with the valid area of a datasource
327 *
328 * @param validArea
329 */
330 private boolean isValidArea( Geometry validArea ) {
331
332 if ( validArea != null ) {
333 try {
334 Envelope env = request.getGetMapRequestCopy().getBoundingBox();
335 Geometry geom = GeometryFactory.createSurface( env, reqCRS );
336 if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
337 // if requested CRS is not identical to the CRS of the valid area
338 // a transformation must be performed before intersection can
339 // be checked
340 GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
341 geom = gt.transform( geom );
342 }
343 return geom.intersects( validArea );
344 } catch ( Exception e ) {
345 // should never happen
346 LOG.logError( "Could not validate WMS datasource area", e );
347 }
348 }
349 return true;
350 }
351
352 /**
353 * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
354 * be thrown. If the layer matches the request, but isn't able to deliver data for the requested area and/or scale
355 * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
356 * true will be returned.
357 *
358 * @param layer
359 * layer as defined at the capabilities/configuration
360 */
361 private boolean validate( Layer layer )
362 throws OGCWebServiceException {
363
364 if ( layer == null ) {
365 return false;
366 }
367
368 // why the layer can be null here is not known, but just in case:
369 String name = layer.getName();
370
371 // check for valid coordinated reference system
372 String[] srs = layer.getSrs();
373 boolean tmp = false;
374 for ( int i = 0; i < srs.length; i++ ) {
375 if ( srs[i].equalsIgnoreCase( getMapRequest.getSrs() ) ) {
376 tmp = true;
377 break;
378 }
379 }
380
381 if ( !tmp ) {
382 throw new InvalidSRSException( Messages.getMessage( "WMS_INVALIDSRS", name, getMapRequest.getSrs() ) );
383 }
384
385 // check bounding box
386 try {
387
388 Envelope bbox = getMapRequest.getBoundingBox();
389 Envelope layerBbox = layer.getLatLonBoundingBox();
390 if ( !getMapRequest.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
391 // transform the bounding box of the request to EPSG:4326
392 GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
393 bbox = gt.transform( bbox, reqCRS );
394 }
395
396 if ( !bbox.intersects( layerBbox ) ) {
397 return false;
398 }
399
400 } catch ( Exception e ) {
401 throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
402 }
403
404 return true;
405 }
406
407 /**
408 * creates a <tt>GetFeatureInfoResult</tt> containing an <tt>OGCWebServiceException</tt>
409 *
410 * @param e
411 * exception to encapsulate into the response
412 */
413 private GetFeatureInfoResult createExceptionResponse( Exception e ) {
414
415 OGCWebServiceException exce = null;
416
417 // default --> application/vnd.ogc.se_xml
418 exce = new OGCWebServiceException( getClass().getName(), e.getMessage() );
419
420 GetFeatureInfoResult res = WMSProtocolFactory.createGetFeatureInfoResponse( request, exce, null );
421
422 return res;
423 }
424
425 /**
426 * generates the desired output from the GMLs
427 *
428 * @return the result object
429 * @throws OGCWebServiceException
430 */
431 private GetFeatureInfoResult createFeatureInfoResponse()
432 throws OGCWebServiceException {
433
434 Envelope bbox = getMapRequest.getBoundingBox();
435
436 StringBuffer sb = new StringBuffer( 20000 );
437 sb.append( "<ll:FeatureCollection " );
438
439 URL schemaLoc = configuration.getDeegreeParams().getFeatureSchemaLocation();
440 if ( schemaLoc != null ) {
441 sb.append( "xsi:schemaLocation='" );
442 sb.append( configuration.getDeegreeParams().getFeatureSchemaNamespace() );
443 sb.append( " " );
444 sb.append( schemaLoc.toExternalForm() );
445 sb.append( "'" );
446 }
447
448 sb.append( " xmlns:gml='http://www.opengis.net/gml' " );
449 sb.append( "xmlns:ll='http://www.lat-lon.de' " );
450 sb.append( "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " );
451 URL url = configuration.getDeegreeParams().getSchemaLocation();
452 if ( url != null ) {
453 sb.append( "xsi:schemaLocation='" );
454 sb.append( "http://www.lat-lon.de " + url.toExternalForm() + "'" );
455 }
456 sb.append( "><gml:boundedBy>" );
457 sb.append( "<gml:Box srsName='" + getMapRequest.getSrs() + "'>" );
458 sb.append( "<gml:coordinates>" + bbox.getMin().getX() + "," );
459 sb.append( bbox.getMin().getY() + " " + bbox.getMax().getX() + "," );
460 sb.append( bbox.getMax().getY() + "</gml:coordinates>" );
461 sb.append( "</gml:Box></gml:boundedBy>" );
462
463 int cnt = 0;
464
465 for ( int i = 0; i < featCol.length; i++ ) {
466 if ( featCol[i] instanceof OGCWebServiceException ) {
467 throw (OGCWebServiceException) featCol[i];
468 }
469 if ( featCol[i] != null ) {
470 FeatureCollection fc = (FeatureCollection) featCol[i];
471 cnt = appendFeatureCollection( fc, sb, cnt );
472 }
473
474 // if ( cnt >= request.getFeatureCount() ) break;
475 }
476 sb.append( "</ll:FeatureCollection>" );
477 LOG.logDebug( "original feature info response: ", sb );
478 GetFeatureInfoResult response = WMSProtocolFactory.createGetFeatureInfoResponse( request, null, sb.toString() );
479
480 return response;
481 }
482
483 /**
484 *
485 * @param col
486 * @param sb
487 * @param cnt
488 * @return a counter, probably the same that is given as argument
489 */
490 private int appendFeatureCollection( FeatureCollection col, StringBuffer sb, int cnt ) {
491
492 Feature[] feat = col.toArray();
493 if ( feat != null ) {
494 for ( int j = 0; j < feat.length; j++ ) {
495 FeatureType ft = feat[j].getFeatureType();
496 PropertyType[] ftp = ft.getProperties();
497 cnt++;
498 sb.append( "<gml:featureMember>" );
499 sb.append( "<ll:" ).append( ft.getName().getLocalName() );
500 sb.append( " fid='" ).append( feat[j].getId().replace( ' ', '_' ) ).append( "'>" );
501 for ( int i = 0; i < ftp.length; i++ ) {
502 if ( ftp[i].getType() != Types.GEOMETRY && ftp[i].getType() != Types.POINT
503 && ftp[i].getType() != Types.CURVE && ftp[i].getType() != Types.SURFACE
504 && ftp[i].getType() != Types.MULTIPOINT && ftp[i].getType() != Types.MULTICURVE
505 && ftp[i].getType() != Types.MULTISURFACE ) {
506
507 FeatureProperty[] props = feat[j].getProperties( ftp[i].getName() );
508 if ( props != null ) {
509 for ( FeatureProperty property : props ) {
510 Object value = property.getValue();
511 sb.append( "<ll:" + ftp[i].getName().getLocalName() + ">" );
512 if ( value instanceof FeatureCollection ) {
513 FeatureCollection fc = (FeatureCollection) value;
514 appendFeatureCollection( fc, sb, cnt );
515 } else {
516 sb.append( "<![CDATA[" ).append( value ).append( "]]>" );
517 }
518 sb.append( "</ll:" + ftp[i].getName().getLocalName() + ">" );
519 }
520 }
521 }
522 }
523 sb.append( "</ll:" ).append( ft.getName().getLocalName() ).append( '>' );
524 sb.append( "</gml:featureMember>" );
525 if ( cnt >= request.getFeatureCount() )
526 break;
527 }
528 }
529
530 return cnt;
531 }
532
533 // //////////////////////////////////////////////////////////////////////////
534 // inner classes //
535 // //////////////////////////////////////////////////////////////////////////
536
537 /**
538 * Inner class for accessing the data of one layer and creating a GML document from it. The class extends
539 * <tt>Thread</tt> and implements the run method, so that a parallel data accessing from several layers is possible.
540 *
541 * @version $Revision: 24806 $
542 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
543 */
544 public class ServiceInvoker implements Callable<Object> {
545 private Layer layer = null;
546
547 private AbstractDataSource datasource = null;
548
549 /**
550 * Creates a new ServiceInvoker object.
551 *
552 * @param layer
553 * @param datasource
554 */
555 ServiceInvoker( Layer layer, AbstractDataSource datasource ) {
556 this.layer = layer;
557 this.datasource = datasource;
558 }
559
560 /**
561 * central method for access the data assigned to a data source
562 *
563 * @return result of feature info query
564 */
565 public Object call() {
566 Object response = null;
567 if ( datasource != null ) {
568 Callable<Object> task = null;
569 try {
570 int type = datasource.getType();
571 switch ( type ) {
572 case AbstractDataSource.LOCALWFS:
573 case AbstractDataSource.REMOTEWFS: {
574 OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource );
575 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
576 break;
577 }
578 case AbstractDataSource.LOCALWCS:
579 case AbstractDataSource.REMOTEWCS: {
580 OGCWebServiceRequest request = GetMapServiceInvokerForNL.createGetCoverageRequest( datasource,
581 getMapRequest );
582 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
583 break;
584 }
585 case AbstractDataSource.REMOTEWMS: {
586 OGCWebServiceRequest request = createGetFeatureInfo( datasource );
587 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
588 break;
589 }
590 case AbstractDataSource.DATABASE: {
591 DatabaseDataSource dbds = (DatabaseDataSource) datasource;
592 Envelope target = calcTargetArea( dbds, dbds.getNativeCRS().getPrefixedName() );
593
594 for ( Dimension dim : configuration.getLayer( layer.getName() ).getDimension() ) {
595 if ( dim.getDefaultValue() != null ) {
596 if ( dim.getName().equals( "time" )
597 && request.getGetMapRequestCopy().getDimTime() == null ) {
598 request.getGetMapRequestCopy().setDimTime(
599 new DimensionValues(
600 dim.getDefaultValue() ) );
601 } else if ( dim.getName().equals( "elevation" )
602 && request.getGetMapRequestCopy().getDimElev() == null ) {
603 request.getGetMapRequestCopy().setDimElev(
604 new DimensionValues(
605 dim.getDefaultValue() ) );
606 }
607 }
608 }
609
610 task = new DoDatabaseQueryTask( (DatabaseDataSource) datasource, target, null,
611 datasource.getDimProps(), request.getGetMapRequestCopy() );
612 break;
613 }
614 case AbstractDataSource.EXTERNALDATAACCESS: {
615 ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess();
616 task = new DoExternalAccessTask( eda, request );
617 break;
618 }
619 }
620 } catch ( Exception e ) {
621 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvoker: " + layer.getName(),
622 Messages.getMessage( "WMS_CREATE_QUERY" ) );
623 response = exce;
624 LOG.logError( Messages.getMessage( "WMS_CREATE_QUERY" ) + ": " + e.getMessage(), e );
625 throw new RuntimeException( e );
626 }
627
628 try {
629 Executor executor = Executor.getInstance();
630 Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 );
631 response = handleResponse( o );
632 } catch ( CancellationException e ) {
633 // exception can't be re-thrown because responsible GetMapHandler
634 // must collect all responses of all data sources
635 String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE",
636 new Integer( datasource.getRequestTimeLimit() ) );
637 LOG.logError( s, e );
638 if ( datasource.isFailOnException() ) {
639 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
640 response = exce;
641 } else {
642 response = null;
643 }
644 } catch ( Throwable t ) {
645 // exception can't be re-thrown because responsible GetMapHandler
646 // must collect all responses of all data sources
647 String s = Messages.getMessage( "WMS_ERRORDOSERVICE", t.getMessage() );
648 LOG.logError( s, t );
649 if ( datasource.isFailOnException() ) {
650 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
651 response = exce;
652 } else {
653 response = null;
654 }
655 }
656
657 }
658
659 return response;
660
661 }
662
663 /**
664 * creates a getFeature request considering the getMap request and the filter conditions defined in the
665 * submitted <tt>DataSource</tt> object. The request will be encapsulated within a <tt>OGCWebServiceEvent</tt>.
666 *
667 * @param ds
668 * @return GetFeature request object
669 */
670 private GetFeature createGetFeatureRequest( LocalWFSDataSource ds )
671 throws Exception {
672
673 WFSCapabilities capa;
674 if ( ds.getOGCWebService() instanceof WFService ) {
675 WFService se = (WFService) ds.getOGCWebService();
676 capa = se.getCapabilities();
677 } else {
678 RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService();
679 capa = wfs.getWFSCapabilities();
680 }
681 QualifiedName gn = ds.getName();
682 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
683
684 if ( ft == null ) {
685 throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) );
686 }
687 String crs = ft.getDefaultSRS().toASCIIString();
688 Envelope targetArea = calcTargetArea( ds, crs );
689
690 // no filter condition has been defined
691 StringBuffer sb = new StringBuffer( 2000 );
692 sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
693 sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " );
694 sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
695 sb.append( "xmlns:wfs='http://www.opengis.net/wfs' " );
696 sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
697 sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' );
698 sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " );
699 sb.append( "service='WFS' version='1.1.0' " );
700 if ( ds.getType() == AbstractDataSource.LOCALWFS ) {
701 sb.append( "outputFormat='FEATURECOLLECTION'>" );
702 } else {
703 sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
704 }
705 sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" );
706
707 Query query = ds.getQuery();
708
709 // append <wfs:PropertyName> elements
710 if ( query != null && query.getPropertyNames() != null ) {
711 PropertyPath[] propertyNames = query.getPropertyNames();
712 for ( PropertyPath path : propertyNames ) {
713 NamespaceContext nsContext = path.getNamespaceContext();
714 sb.append( "<wfs:PropertyName" );
715 Map<String, URI> namespaceMap = nsContext.getNamespaceMap();
716 Iterator<String> prefixIter = namespaceMap.keySet().iterator();
717 while ( prefixIter.hasNext() ) {
718 String prefix = prefixIter.next();
719 if ( !CommonNamespaces.XMLNS_PREFIX.equals( prefix ) ) {
720 URI namespace = namespaceMap.get( prefix );
721 sb.append( " xmlns:" + prefix + "=\"" + namespace + "\"" );
722 }
723 }
724 sb.append( '>' );
725 sb.append( path.getAsString() );
726 sb.append( "</wfs:PropertyName>" );
727 }
728 }
729
730 sb.append( "<ogc:Filter>" );
731 if ( query == null ) {
732 // BBOX operation for speeding up the search at simple datasources
733 // like shapes
734 sb.append( "<ogc:BBOX><PropertyName>" );
735 sb.append( ds.getGeometryProperty().getPrefixedName() );
736 sb.append( "</PropertyName>" );
737 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) );
738 sb.append( "</ogc:BBOX>" );
739 sb.append( "</ogc:Filter></Query></GetFeature>" );
740 } else {
741 Filter filter = query.getFilter();
742
743 sb.append( "<ogc:And>" );
744 // BBOX operation for speeding up the search at simple datasources
745 // like shapes
746 sb.append( "<ogc:BBOX><PropertyName>" + ds.getGeometryProperty().getPrefixedName() );
747 sb.append( "</PropertyName>" );
748 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) );
749 sb.append( "</ogc:BBOX>" );
750
751 if ( filter instanceof ComplexFilter ) {
752 org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation();
753 sb.append( op.toXML() );
754 } else {
755 ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
756 for ( int i = 0; i < featureIds.size(); i++ ) {
757 FeatureId fid = featureIds.get( i );
758 sb.append( fid.toXML() );
759 }
760 }
761 sb.append( "</ogc:And></ogc:Filter></Query></GetFeature>" );
762 }
763
764 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
765 LOG.logDebug( "GetFeature-request:\n" + sb );
766 }
767
768 // create dom representation of the request
769 StringReader sr = new StringReader( sb.toString() );
770 Document doc = XMLTools.parse( sr );
771
772 // create OGCWebServiceEvent object
773 IDGenerator idg = IDGenerator.getInstance();
774 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
775
776 return gfr;
777 }
778
779 /**
780 * calculates the target area for the getfeatureinfo request from the maps bounding box, the its size and the
781 * image coordinates of interest. An area is calculated instead of using a point because to consider
782 * uncertainties determining the point of interest
783 *
784 * @param ds
785 * <tt>DataSource</tt> of the layer that is requested for feature infos (each layer may be offered in
786 * its own crs)
787 * @param crs
788 */
789 private Envelope calcTargetArea( AbstractDataSource ds, String crs )
790 throws OGCWebServiceException {
791
792 int width = request.getGetMapRequestCopy().getWidth();
793 int height = request.getGetMapRequestCopy().getHeight();
794 int x = request.getClickPoint().x;
795 int y = request.getClickPoint().y;
796
797 Envelope bbox = request.getGetMapRequestCopy().getBoundingBox();
798
799 // transform request bounding box to the coordinate reference
800 // system the WFS holds the data if requesting CRS and WFS-Data
801 // crs are different
802 Envelope tBbox = null;
803 try {
804 GeoTransform gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(),
805 bbox.getMax().getX(), bbox.getMax().getY(), 0, 0,
806 width - 1, height - 1 );
807 double[] target = new double[4];
808 int rad = configuration.getDeegreeParams().getFeatureInfoRadius();
809 target[0] = gt.getSourceX( x - rad );
810 target[1] = gt.getSourceY( y + rad );
811 target[2] = gt.getSourceX( x + rad );
812 target[3] = gt.getSourceY( y - rad );
813
814 tBbox = GeometryFactory.createEnvelope( target[0], target[1], target[2], target[3], null );
815 if ( !( crs.equalsIgnoreCase( request.getGetMapRequestCopy().getSrs() ) ) ) {
816 GeoTransformer transformer = new GeoTransformer( CRSFactory.create( crs ) );
817 tBbox = transformer.transform( tBbox, reqCRS );
818 }
819
820 } catch ( Exception e ) {
821 throw new OGCWebServiceException( e.toString() );
822 }
823
824 return tBbox;
825 }
826
827 /**
828 * creates a GetFeatureInfo request for requesting a cascaded remote WMS The request will be encapsulated within
829 * a <tt>OGCWebServiceEvent</tt>.
830 *
831 * @param ds
832 * @return GetFeatureInfo request object
833 */
834 private GetFeatureInfo createGetFeatureInfo( AbstractDataSource ds ) {
835
836 // create embbeded map request
837 GetMap gmr = ( (RemoteWMSDataSource) ds ).getGetMapRequest();
838
839 String format = getMapRequest.getFormat();
840
841 if ( gmr != null && !"%default%".equals( gmr.getFormat() ) ) {
842 format = gmr.getFormat();
843 }
844
845 org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] lys = null;
846 lys = new org.deegree.ogcwebservices.wms.operation.GetMap.Layer[1];
847 lys[0] = GetMap.createLayer( layer.getName(), "$DEFAULT" );
848
849 if ( gmr != null && gmr.getLayers() != null ) {
850 lys = gmr.getLayers();
851 }
852 Color bgColor = getMapRequest.getBGColor();
853 if ( gmr != null && gmr.getBGColor() != null ) {
854 bgColor = gmr.getBGColor();
855 }
856 Values time = getMapRequest.getTime();
857 if ( gmr != null && gmr.getTime() != null ) {
858 time = gmr.getTime();
859 }
860 Map<String, String> vendorSpecificParameter = getMapRequest.getVendorSpecificParameters();
861 if ( gmr != null && gmr.getVendorSpecificParameters() != null
862 && gmr.getVendorSpecificParameters().size() > 0 ) {
863 vendorSpecificParameter = gmr.getVendorSpecificParameters();
864 }
865 String version = "1.1.0";
866 if ( gmr != null && gmr.getVersion() != null ) {
867 version = gmr.getVersion();
868 }
869 Values elevation = getMapRequest.getElevation();
870 if ( gmr != null && gmr.getElevation() != null ) {
871 elevation = gmr.getElevation();
872 }
873 Map<String, Values> sampleDim = null;
874 if ( gmr != null && gmr.getSampleDimension() != null ) {
875 sampleDim = gmr.getSampleDimension();
876 }
877
878 IDGenerator idg = IDGenerator.getInstance();
879 gmr = GetMap.create( version, "" + idg.generateUniqueID(), lys, elevation, sampleDim, format,
880 getMapRequest.getWidth(), getMapRequest.getHeight(), getMapRequest.getSrs(),
881 getMapRequest.getBoundingBox(), getMapRequest.getTransparency(), bgColor,
882 getMapRequest.getExceptions(), time, null, null, vendorSpecificParameter );
883
884 // create GetFeatureInfo request for cascaded/remote WMS
885 String[] queryLayers = new String[] { ds.getName().getPrefixedName() };
886 GetFeatureInfo req = GetFeatureInfo.create( version, this.toString(), queryLayers, gmr,
887 "application/vnd.ogc.gml", request.getFeatureCount(),
888 request.getClickPoint(), request.getExceptions(), null,
889 request.getVendorSpecificParameters() );
890
891 try {
892 LOG.logDebug( "cascaded GetFeatureInfo request: ", req.getRequestParameter() );
893 } catch ( OGCWebServiceException e ) {
894 LOG.logError( e.getMessage(), e );
895 }
896
897 return req;
898 }
899
900 /**
901 * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS implementation accessed by
902 * this class is able to return the result of a request by calling the write-method.
903 *
904 * @param result
905 * to a GetXXX request
906 * @return the response object
907 * @throws Exception
908 */
909 private Object handleResponse( Object result )
910 throws Exception {
911 Object response = null;
912 if ( result instanceof FeatureResult ) {
913 response = handleGetFeatureResponse( (FeatureResult) result );
914 } else if ( result instanceof ResultCoverage ) {
915 response = handleGetCoverageResponse( (ResultCoverage) result );
916 } else if ( result instanceof GetFeatureInfoResult ) {
917 response = handleGetFeatureInfoResult( (GetFeatureInfoResult) result );
918 } else {
919 throw new Exception( Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ) );
920 }
921 return response;
922 }
923
924 /**
925 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from
926 * it
927 *
928 * @param response
929 * @return the response objects
930 * @throws Exception
931 */
932 private Object handleGetFeatureResponse( FeatureResult response )
933 throws Exception {
934 FeatureCollection fc = null;
935
936 Object o = response.getResponse();
937 if ( o instanceof FeatureCollection ) {
938 fc = (FeatureCollection) o;
939 } else if ( o.getClass() == byte[].class ) {
940 Reader reader = new InputStreamReader( new ByteArrayInputStream( (byte[]) o ) );
941 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
942 doc.load( reader, XMLFragment.DEFAULT_URL );
943 fc = doc.parse();
944 } else {
945 throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) );
946 }
947 return fc;
948 }
949
950 /**
951 *
952 * @param res
953 */
954 private Object handleGetFeatureInfoResult( GetFeatureInfoResult res )
955 throws Exception {
956
957 FeatureCollection fc = null;
958 StringReader sr = new StringReader( res.getFeatureInfo() );
959 XMLFragment xml = new XMLFragment( sr, XMLFragment.DEFAULT_URL );
960
961 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
962 LOG.logDebug( "GetFeature-response (before transformation): " + xml.getAsPrettyString() );
963 }
964
965 if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) {
966 URL url = ( (RemoteWMSDataSource) datasource ).getFeatureInfoTransform();
967 if ( url != null ) {
968 // transform incoming GML/XML to a GML application schema
969 // that is understood by deegree
970 XSLTDocument xslt = new XSLTDocument();
971 xslt.load( url );
972 xml = xslt.transform( xml, null, null, null );
973 }
974 }
975 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
976 doc.setRootElement( xml.getRootElement() );
977 fc = doc.parse();
978 return fc;
979 }
980
981 private Object handleGetCoverageResponse( ResultCoverage response )
982 throws OGCWebServiceException {
983 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
984 if ( gc != null ) {
985 BufferedImage bi = gc.getAsImage( -1, -1 );
986
987 float[][] data = new Image2RawData( bi ).parse();
988
989 double scaleX = (double) data[0].length / (double) getMapRequest.getWidth();
990 double scaleY = (double) data.length / (double) getMapRequest.getHeight();
991 int pxSizeX = (int) Math.round( scaleX );
992 int pxSizeY = (int) Math.round( scaleY );
993 if ( pxSizeX == 0 ) {
994 pxSizeX = 1;
995 }
996 if ( pxSizeY == 0 ) {
997 pxSizeY = 1;
998 }
999
1000 LOG.logDebug( "Size of grid coverage is " + data[0].length + "x" + data.length + "." );
1001 LOG.logDebug( "Returning an area of " + pxSizeX + "x" + pxSizeY + " pixels." );
1002
1003 int ix = (int) ( request.getClickPoint().x * scaleX ) - pxSizeX / 2;
1004 int iy = (int) ( request.getClickPoint().y * scaleY ) - pxSizeY / 2;
1005
1006 // some checks to avoid areas that are not requestable
1007 if ( ix < 0 ) {
1008 ix = 0;
1009 }
1010 if ( iy < 0 ) {
1011 iy = 0;
1012 }
1013 if ( ix >= ( data[0].length - pxSizeX ) ) {
1014 ix = data[0].length - pxSizeX - 1;
1015 }
1016 if ( iy >= ( data.length - pxSizeY ) ) {
1017 iy = data.length - pxSizeY - 1;
1018 }
1019
1020 FeatureCollection fc = FeatureFactory.createFeatureCollection( gc.getCoverageOffering().getName(),
1021 pxSizeX * pxSizeY );
1022
1023 PropertyType pt = null;
1024 try {
1025 pt = FeatureFactory.createPropertyType( VALUE, new QualifiedName( "xsd", "double",
1026 CommonNamespaces.XSNS ), 1, 1 );
1027 } catch ( UnknownTypeException e ) {
1028 LOG.logError( "The xsd:double type is not known?!? Get a new deegree.jar!", e );
1029 }
1030 FeatureType ft = FeatureFactory.createFeatureType( gc.getCoverageOffering().getName(), false,
1031 new PropertyType[] { pt } );
1032
1033 for ( int x = ix; x < ix + pxSizeX; ++x ) {
1034 for ( int y = iy; y < iy + pxSizeY; ++y ) {
1035 FeatureProperty p = FeatureFactory.createFeatureProperty( VALUE, new Double( data[y][x] ) );
1036 Feature f = FeatureFactory.createFeature( "ID_faked_for_" + x + "x" + y, ft,
1037 new FeatureProperty[] { p } );
1038 fc.add( f );
1039 }
1040 }
1041
1042 return fc;
1043 }
1044
1045 throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE",
1046 datasource.getName() ) );
1047
1048 }
1049
1050 }
1051
1052 }