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