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