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