001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wms/GetMapServiceInvokerForNL.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 org.deegree.graphics.sld.StyleUtils.extractRequiredProperties;
039    import static org.deegree.i18n.Messages.get;
040    import static org.deegree.model.spatialschema.GMLGeometryAdapter.exportAsBox;
041    import static org.deegree.ogcbase.ExceptionCode.INVALIDDIMENSIONVALUE;
042    import static org.deegree.ogcbase.ExceptionCode.MISSINGDIMENSIONVALUE;
043    import static org.deegree.ogcbase.PropertyPathFactory.createPropertyPath;
044    
045    import java.awt.Color;
046    import java.awt.Graphics;
047    import java.awt.image.BufferedImage;
048    import java.io.StringReader;
049    import java.net.URI;
050    import java.util.ArrayList;
051    import java.util.Iterator;
052    import java.util.LinkedList;
053    import java.util.List;
054    import java.util.Map;
055    import java.util.concurrent.Callable;
056    import java.util.concurrent.CancellationException;
057    
058    import org.deegree.datatypes.QualifiedName;
059    import org.deegree.datatypes.Types;
060    import org.deegree.framework.concurrent.DoDatabaseQueryTask;
061    import org.deegree.framework.concurrent.DoExternalAccessTask;
062    import org.deegree.framework.concurrent.DoServiceTask;
063    import org.deegree.framework.concurrent.Executor;
064    import org.deegree.framework.log.ILogger;
065    import org.deegree.framework.log.LoggerFactory;
066    import org.deegree.framework.util.CharsetUtils;
067    import org.deegree.framework.util.IDGenerator;
068    import org.deegree.framework.xml.XMLFragment;
069    import org.deegree.framework.xml.XMLTools;
070    import org.deegree.graphics.MapFactory;
071    import org.deegree.graphics.Theme;
072    import org.deegree.graphics.sld.FeatureTypeConstraint;
073    import org.deegree.graphics.sld.LayerFeatureConstraints;
074    import org.deegree.graphics.sld.NamedLayer;
075    import org.deegree.graphics.sld.UserStyle;
076    import org.deegree.i18n.Messages;
077    import org.deegree.model.coverage.grid.GridCoverage;
078    import org.deegree.model.coverage.grid.ImageGridCoverage;
079    import org.deegree.model.coverage.grid.WorldFile;
080    import org.deegree.model.crs.CRSFactory;
081    import org.deegree.model.crs.CRSTransformationException;
082    import org.deegree.model.crs.CoordinateSystem;
083    import org.deegree.model.crs.GeoTransformer;
084    import org.deegree.model.crs.UnknownCRSException;
085    import org.deegree.model.feature.Feature;
086    import org.deegree.model.feature.FeatureCollection;
087    import org.deegree.model.feature.FeatureProperty;
088    import org.deegree.model.feature.schema.FeatureType;
089    import org.deegree.model.feature.schema.PropertyType;
090    import org.deegree.model.filterencoding.ComplexFilter;
091    import org.deegree.model.filterencoding.FeatureFilter;
092    import org.deegree.model.filterencoding.FeatureId;
093    import org.deegree.model.filterencoding.Filter;
094    import org.deegree.model.spatialschema.Envelope;
095    import org.deegree.model.spatialschema.Geometry;
096    import org.deegree.model.spatialschema.GeometryException;
097    import org.deegree.model.spatialschema.GeometryFactory;
098    import org.deegree.model.spatialschema.WKTAdapter;
099    import org.deegree.ogcbase.PropertyPath;
100    import org.deegree.ogcbase.PropertyPathFactory;
101    import org.deegree.ogcbase.SortProperty;
102    import org.deegree.ogcwebservices.InconsistentRequestException;
103    import org.deegree.ogcwebservices.InvalidParameterValueException;
104    import org.deegree.ogcwebservices.OGCWebService;
105    import org.deegree.ogcwebservices.OGCWebServiceException;
106    import org.deegree.ogcwebservices.OGCWebServiceRequest;
107    import org.deegree.ogcwebservices.wcs.WCSException;
108    import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage;
109    import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
110    import org.deegree.ogcwebservices.wfs.RemoteWFService;
111    import org.deegree.ogcwebservices.wfs.WFService;
112    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
113    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
114    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
115    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
116    import org.deegree.ogcwebservices.wfs.operation.Query;
117    import org.deegree.ogcwebservices.wms.capabilities.Dimension;
118    import org.deegree.ogcwebservices.wms.capabilities.Layer;
119    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
120    import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
121    import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource;
122    import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource;
123    import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
124    import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource;
125    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
126    import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess;
127    import org.deegree.ogcwebservices.wms.operation.DimensionValues;
128    import org.deegree.ogcwebservices.wms.operation.GetMap;
129    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
130    import org.deegree.ogcwebservices.wms.operation.DimensionValues.DimensionValue;
131    import org.w3c.dom.Document;
132    
133    /**
134     * Class for accessing the data of one layers datasource and creating a <tt>Theme</tt> from it.
135     * 
136     * @version $Revision: 20188 $
137     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
138     * @author last edited by: $Author: aschmitz $
139     * 
140     * @version 1.0. $Revision: 20188 $, $Date: 2009-10-19 12:14:30 +0200 (Mo, 19. Okt 2009) $
141     * 
142     * @since 2.0
143     */
144    public class GetMapServiceInvokerForNL extends GetMapServiceInvoker implements Callable<Object> {
145    
146        private static final ILogger LOG = LoggerFactory.getLogger( GetMapServiceInvokerForNL.class );
147    
148        private final GetMap request;
149    
150        private NamedLayer layer = null;
151    
152        private UserStyle style = null;
153    
154        private AbstractDataSource datasource = null;
155    
156        /**
157         * Creates a new ServiceInvokerForNL object.
158         * 
159         * @param handler
160         * @param layer
161         * @param datasource
162         * @param style
163         * @param scale
164         */
165        GetMapServiceInvokerForNL( DefaultGetMapHandler handler, NamedLayer layer, AbstractDataSource datasource,
166                                   UserStyle style, double scale ) {
167    
168            super( handler, scale );
169    
170            this.layer = layer;
171            this.request = handler.getRequest();
172            this.style = style;
173            this.datasource = datasource;
174        }
175    
176        /**
177         * @return the callable actually fetching the data
178         * @throws Exception
179         */
180        public Callable<Object> getWrappedCallable()
181                                throws Exception {
182            int type = datasource.getType();
183            switch ( type ) {
184            case AbstractDataSource.LOCALWFS:
185            case AbstractDataSource.REMOTEWFS: {
186                OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource );
187                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
188            }
189            case AbstractDataSource.LOCALWCS:
190            case AbstractDataSource.REMOTEWCS: {
191                OGCWebServiceRequest request = createGetCoverageRequest( datasource, this.request );
192                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
193            }
194            case AbstractDataSource.REMOTEWMS: {
195                String styleName = null;
196    
197                if ( style != null ) {
198                    styleName = style.getName();
199                }
200    
201                OGCWebServiceRequest request = GetMap.createGetMapRequest( datasource, handler.getRequest(), styleName,
202                                                                           layer.getName() );
203                LOG.logDebug( "GetMap request: " + request.toString() );
204                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
205            }
206            case AbstractDataSource.DATABASE: {
207                CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
208                Envelope env = this.request.getBoundingBox();
209                env = GeometryFactory.createEnvelope( env.getMin(), env.getMax(), crs );
210    
211                // set dim default values TODO do so in general?
212                for ( Dimension dim : handler.getConfiguration().getLayer( layer.getName() ).getDimension() ) {
213                    if ( dim.getDefaultValue() != null ) {
214                        if ( dim.getName().equals( "time" ) && request.getDimTime() == null ) {
215                            request.setDimTime( new DimensionValues( dim.getDefaultValue() ) );
216                        } else if ( dim.getName().equals( "elevation" ) && request.getDimElev() == null ) {
217                            request.setDimElev( new DimensionValues( dim.getDefaultValue() ) );
218                        }
219                    }
220                }
221    
222                if ( handler.sqls != null ) {
223                    return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env,
224                                                    handler.sqls.get( layer.getName() ), datasource.getDimProps(), request );
225                }
226                return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env, null, datasource.getDimProps(),
227                                                request );
228            }
229            case AbstractDataSource.EXTERNALDATAACCESS: {
230                ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess();
231                return new DoExternalAccessTask( eda, request );
232            }
233            default:
234                return null;
235            }
236        }
237    
238        public Object call()
239                                throws OGCWebServiceException {
240    
241            Object response = null;
242            if ( datasource != null ) {
243                Callable<Object> task = null;
244                try {
245                    task = getWrappedCallable();
246                } catch ( OGCWebServiceException e ) {
247                    throw e;
248                } catch ( Exception e ) {
249                    LOG.logError( "Exception during fetching data for some data source", e );
250                    OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(),
251                                                                              Messages.getMessage( "WMS_ERRORQUERYCREATE",
252                                                                                                   e ) );
253                    // exception can't be re-thrown because responsible GetMapHandler
254                    // must collect all responses of all data sources
255                    response = exce;
256                }
257    
258                try {
259                    // start reading data with a limited time frame. The time limit
260                    // read from the data source must be multiplied by 1000 because
261                    // the method expects milliseconds as time limit
262                    Executor executor = Executor.getInstance();
263                    Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 );
264                    response = handleResponse( o );
265                } catch ( CancellationException e ) {
266                    // exception can't be re-thrown because responsible GetMapHandler
267                    // must collect all responses of all data sources
268                    String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE", new Integer( datasource.getRequestTimeLimit() ) );
269                    LOG.logError( s, e );
270                    if ( datasource.isFailOnException() ) {
271                        OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
272                        response = exce;
273                    } else {
274                        response = null;
275                    }
276                } catch ( WMSExceptionFromWCS e ) {
277                    if ( datasource.isFailOnException() ) {
278                        response = e;
279                    }
280                } catch ( Throwable e ) {
281                    // exception can't be re-thrown because responsible GetMapHandler
282                    // must collect all responses of all data sources
283                    String s = Messages.getMessage( "WMS_ERRORDOSERVICE", e.getMessage() );
284                    LOG.logError( s, e );
285                    if ( datasource.isFailOnException() ) {
286                        OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
287                        response = exce;
288                    } else {
289                        response = null;
290                    }
291                }
292            }
293    
294            LOG.logDebug( "Layer " + layer.getName() + " returned." );
295    
296            return response;
297        }
298    
299        /**
300         * creates a getFeature request considering the getMap request and the filterconditions defined in the submitted
301         * <tt>DataSource</tt> object. The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>.
302         * 
303         * @param ds
304         * @return GetFeature request object
305         * @throws Exception
306         */
307        private GetFeature createGetFeatureRequest( LocalWFSDataSource ds )
308                                throws Exception {
309    
310            Envelope bbox = transformBBOX( ds );
311    
312            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(), sortProperties = new LinkedList<StringBuffer>();
313    
314            List<PropertyPath> pp = null;
315            if ( style != null ) {
316                List<UserStyle> styleList = new ArrayList<UserStyle>();
317                styleList.add( style );
318                pp = extractRequiredProperties( styleList, scaleDen );
319            } else {
320                pp = new ArrayList<PropertyPath>();
321            }
322            PropertyPath geomPP = PropertyPathFactory.createPropertyPath( ds.getGeometryProperty() );
323            if ( !pp.contains( geomPP ) ) {
324                pp.add( geomPP );
325            }
326    
327            // handling of vendor specific parameters "filterproperty" and "filtervalue"
328            String filterProperty = request.getVendorSpecificParameter( "FILTERPROPERTY" );
329            String filterValue = request.getVendorSpecificParameter( "FILTERVALUE" );
330    
331            boolean useCustomFilter = handler.getConfiguration().getDeegreeParams().getFiltersAllowed();
332            useCustomFilter = useCustomFilter && filterProperty != null && filterValue != null;
333    
334            if ( useCustomFilter ) {
335                LOG.logDebug( "Using custom filter on " + filterProperty + " = " + filterValue );
336                PropertyPath path = createPropertyPath( new QualifiedName( filterProperty ) );
337                if ( !pp.contains( path ) ) {
338                    pp.add( path );
339                }
340            }
341    
342            LOG.logDebug( "required properties: ", pp );
343            Map<String, URI> namesp = extractNameSpaceDef( pp );
344    
345            // no filter condition has been defined
346            StringBuffer sb = new StringBuffer( 5000 );
347            sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
348            sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " );
349            sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
350            sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
351            sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' );
352            sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " );
353            Iterator<String> iter = namesp.keySet().iterator();
354            while ( iter.hasNext() ) {
355                String pre = iter.next();
356                URI nsp = namesp.get( pre );
357                if ( !pre.equals( "xmlns" ) && !pre.equals( ds.getName().getPrefix() ) ) {
358                    sb.append( "xmlns:" ).append( pre ).append( "='" );
359                    sb.append( nsp.toASCIIString() ).append( "' " );
360                }
361            }
362    
363            sb.append( "service='WFS' version='1.1.0' " );
364            if ( ds.getType() == AbstractDataSource.LOCALWFS ) {
365                sb.append( "outputFormat='FEATURECOLLECTION'>" );
366            } else {
367                sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
368            }
369            sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" );
370            /*
371             * this can not be used if charts shale be generated dynamicly as point symbols for ( int j = 0; j < pp.size();
372             * j++ ) { if ( !pp.get( j ).getAsString().endsWith( "$SCALE" ) ) { // $SCALE is a dynamicly created property of
373             * each feature // and can not be requested sb.append( "<PropertyName>" ).append( pp.get( j ).getAsString() );
374             * sb.append( "</PropertyName>" ); } }
375             */
376    
377            // add filters to list
378            // BBOX filter
379            StringBuffer sb2 = new StringBuffer( 512 );
380            sb2.append( "<ogc:BBOX>" );
381            sb2.append( "<PropertyName>" );
382            sb2.append( ds.getGeometryProperty().getPrefixedName() );
383            sb2.append( "</PropertyName>" );
384            sb2.append( exportAsBox( bbox ) );
385            sb2.append( "</ogc:BBOX>" );
386            filters.add( sb2 );
387            // custom filter for property value
388            if ( useCustomFilter ) {
389                sb2 = new StringBuffer( 512 );
390                appendCustomFilter( sb2, filterProperty, filterValue );
391                filters.add( sb2 );
392            }
393            // filters of query
394            Query query = ds.getQuery();
395            if ( query != null ) {
396                Filter filter = query.getFilter();
397                filters.addAll( extractFilters( filter ) );
398                SortProperty[] sps = query.getSortProperties();
399                if ( sps != null ) {
400                    for ( SortProperty sp : sps ) {
401                        sortProperties.add( extractSortProperty( sp ) );
402                    }
403                }
404            }
405            // filters from SLD
406            if ( layer != null ) {
407                LayerFeatureConstraints lfc = layer.getLayerFeatureConstraints();
408                if ( lfc != null ) {
409                    FeatureTypeConstraint[] fcs = lfc.getFeatureTypeConstraint();
410                    if ( fcs != null ) {
411                        for ( FeatureTypeConstraint fc : fcs ) {
412                            filters.addAll( extractFilters( fc.getFilter() ) );
413                        }
414                    }
415                }
416            }
417    
418            // filters from the dimensions
419            Layer lay = handler.getConfiguration().getLayer( layer.getName() );
420            Map<String, String> dimProps = ds.getDimProps();
421            if ( lay.getDimension() != null && dimProps != null ) {
422                for ( Dimension dim : lay.getDimension() ) {
423                    if ( dim.getName().equals( "time" ) ) {
424                        filters.add( handleDimension( request.getDimTime(), dim, dimProps.get( "time" ) ) );
425                    }
426                    if ( dim.getName().equals( "elevation" ) ) {
427                        filters.add( handleDimension( request.getDimElev(), dim, dimProps.get( "elevation" ) ) );
428                    }
429                }
430            }
431    
432            // actually append the filters
433            sb.append( "<ogc:Filter>" );
434            if ( filters.size() > 1 ) {
435                sb.append( "<ogc:And>" );
436            }
437            for ( StringBuffer s : filters ) {
438                sb.append( s );
439            }
440            if ( filters.size() > 1 ) {
441                sb.append( "</ogc:And>" );
442            }
443            sb.append( "</ogc:Filter>" );
444            if ( sortProperties.size() > 0 ) {
445                sb.append( "<ogc:SortBy>" );
446                for ( StringBuffer s : sortProperties ) {
447                    sb.append( s );
448                }
449                sb.append( "</ogc:SortBy>" );
450            }
451            sb.append( "</Query></GetFeature>" );
452    
453            // create dom representation of the request
454            Document doc = XMLTools.parse( new StringReader( sb.toString() ) );
455    
456            if ( LOG.isDebug() ) {
457                LOG.logDebug( "GetFeature request: "
458                              + new XMLFragment( doc, "http://www.systemid.org" ).getAsPrettyString() );
459            }
460    
461            // create OGCWebServiceEvent object
462            IDGenerator idg = IDGenerator.getInstance();
463            GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
464    
465            return gfr;
466        }
467    
468        private StringBuffer handleDimension( DimensionValues values, Dimension dim, String dimProp )
469                                throws OGCWebServiceException {
470            if ( values == null && dim.getDefaultValue() != null ) {
471                values = new DimensionValues( dim.getDefaultValue() );
472                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" + dim.getDefaultValue() + " "
473                                            + dim.getUnits() );
474            }
475    
476            if ( values == null ) {
477                throw new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", dim.getName() ),
478                                                          MISSINGDIMENSIONVALUE );
479            }
480            if ( !dim.isMultipleValues() && values.hasMultipleValues() ) {
481                throw new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", dim.getName() ),
482                                                          INVALIDDIMENSIONVALUE );
483            }
484    
485            String singleValue = values.values.peek().value;
486            if ( values.values.size() == 1 && singleValue != null ) {
487                DimensionValues origValues = new DimensionValues( dim.getValues() );
488                if ( !origValues.includesValue( singleValue ) ) {
489                    if ( dim.isNearestValue() ) {
490                        String nearestValue = origValues.getNearestValue( singleValue );
491                        values.values.peek().value = nearestValue;
492                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" + nearestValue + " "
493                                                    + dim.getUnits() );
494                    }
495                }
496            }
497    
498            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
499    
500            for ( DimensionValue val : values.values ) {
501                StringBuffer sb = new StringBuffer( 512 );
502                if ( val.value != null ) {
503                    sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ).append( dimProp );
504                    sb.append( "</ogc:PropertyName><ogc:Literal>" ).append( val.value );
505                    sb.append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
506                } else {
507                    sb.append( "<ogc:PropertyIsBetween><ogc:PropertyName>" ).append( dimProp );
508                    sb.append( "</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>" ).append( val.low );
509                    sb.append( "</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>" ).append( val.high );
510                    sb.append( "</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween>" );
511                }
512                filters.add( sb );
513            }
514    
515            if ( filters.size() == 1 ) {
516                return filters.poll();
517            }
518    
519            StringBuffer sb = new StringBuffer( 512 * filters.size() );
520            sb.append( "<ogc:Or>" );
521            while ( filters.size() > 0 ) {
522                sb.append( filters.poll() );
523            }
524            sb.append( "</ogc:Or>" );
525            return sb;
526        }
527    
528        private static StringBuffer extractSortProperty( SortProperty sp ) {
529            StringBuffer sb = new StringBuffer();
530            sb.append( "<ogc:SortProperty>" );
531            sb.append( "<ogc:PropertyName>" );
532            sb.append( sp.getSortProperty().toString() );
533            sb.append( "</ogc:PropertyName>" );
534            sb.append( "<ogc:SortOrder>" );
535            sb.append( sp.getSortOrder() ? "ASC" : "DESC" );
536            sb.append( "</ogc:SortOrder>" );
537            sb.append( "</ogc:SortProperty>" );
538    
539            return sb;
540        }
541    
542        private static LinkedList<StringBuffer> extractFilters( Filter filter ) {
543            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
544    
545            if ( filter instanceof ComplexFilter ) {
546                filters.add( ( ( (ComplexFilter) filter ).getOperation() ).toXML() );
547            }
548            if ( filter instanceof FeatureFilter ) {
549                ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
550                for ( int i = 0; i < featureIds.size(); i++ ) {
551                    FeatureId fid = featureIds.get( i );
552                    filters.add( fid.toXML() );
553                }
554            }
555    
556            return filters;
557        }
558    
559        private static void appendCustomFilter( StringBuffer sb, String name, String value ) {
560            String[] vals = value.split( "," );
561            if ( vals.length > 1 ) {
562                sb.append( "<ogc:Or>" );
563            }
564            for ( String val : vals ) {
565                sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" );
566                sb.append( name ).append( "</ogc:PropertyName><ogc:Literal>" );
567                sb.append( val ).append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
568            }
569            if ( vals.length > 1 ) {
570                sb.append( "</ogc:Or>" );
571            }
572        }
573    
574        /**
575         * transforms the requested BBOX into the DefaultSRS of the assigned feature type
576         * 
577         * @param ds
578         * @return the envelope
579         * @throws OGCWebServiceException
580         * @throws CRSTransformationException
581         * @throws UnknownCRSException
582         */
583        private Envelope transformBBOX( LocalWFSDataSource ds )
584                                throws OGCWebServiceException, CRSTransformationException, UnknownCRSException {
585            Envelope bbox = request.getBoundingBox();
586            // transform request bounding box to the coordinate reference
587            // system the WFS holds the data if requesting CRS and WFS-Data
588            // crs are different
589            OGCWebService service = ds.getOGCWebService();
590            WFSCapabilities capa;
591            if ( service instanceof RemoteWFService ) {
592                RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService();
593                capa = wfs.getWFSCapabilities();
594            } else {
595                WFService wfs = (WFService) service;
596                capa = wfs.getCapabilities();
597            }
598            // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities();
599            QualifiedName gn = ds.getName();
600            WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
601    
602            if ( ft == null ) {
603                throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) );
604            }
605    
606            // enable different formatations of the crs encoding for GML geometries
607            String GML_SRS = "http://www.opengis.net/gml/srs/";
608            String old_gml_srs = ft.getDefaultSRS().toASCIIString();
609            String old_srs;
610            if ( old_gml_srs.startsWith( GML_SRS ) ) {
611                old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase();
612            } else {
613                old_srs = old_gml_srs;
614            }
615    
616            String new_srs = request.getSrs();
617            String new_gml_srs;
618            if ( old_gml_srs.startsWith( GML_SRS ) ) {
619                new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase();
620            } else {
621                new_gml_srs = new_srs;
622            }
623    
624            if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) {
625                GeoTransformer transformer = new GeoTransformer( CRSFactory.create( old_srs ) );
626                bbox = transformer.transform( bbox, this.handler.getRequestCRS() );
627            }
628            return bbox;
629        }
630    
631        /**
632         * creates a getCoverage request considering the getMap request and the filterconditions defined in the submitted
633         * <tt>DataSource</tt> object The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>.
634         * 
635         * @param ds
636         *            the datasource
637         * @param request
638         *            the GetMap operation
639         * @return GetCoverage request object
640         * @throws InconsistentRequestException
641         */
642        protected static GetCoverage createGetCoverageRequest( AbstractDataSource ds, GetMap request )
643                                throws InconsistentRequestException {
644    
645            Envelope bbox = request.getBoundingBox();
646    
647            double xres = bbox.getWidth() / request.getWidth();
648            double yres = bbox.getHeight() / request.getHeight();
649    
650            WorldFile tmpWorldFile = new WorldFile( xres, yres, 0.0, 0.0, bbox, WorldFile.TYPE.OUTER );
651            bbox = tmpWorldFile.getEnvelope( WorldFile.TYPE.CENTER );
652    
653            GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest();
654    
655            String crs = request.getSrs();
656            // if (gcr != null && gcr.getDomainSubset().getRequestSRS() != null) {
657            // crs = gcr.getDomainSubset().getRequestSRS().getCode();
658            // }
659            String format = request.getFormat();
660            int pos = format.indexOf( '/' );
661            if ( pos > -1 )
662                format = format.substring( pos + 1, format.length() );
663            if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) {
664                format = gcr.getOutput().getFormat().getCode();
665            }
666            if ( format.indexOf( "svg" ) > -1 ) {
667                format = "tiff";
668            }
669            if ( format.startsWith( "png" ) ) {
670                format = "png";
671            }
672    
673            String version = "1.0.0";
674            if ( gcr != null && gcr.getVersion() != null ) {
675                version = gcr.getVersion();
676            }
677            String lay = ds.getName().getPrefixedName();
678            if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) {
679                lay = gcr.getSourceCoverage();
680            }
681            String ipm = null;
682            if ( gcr != null && gcr.getInterpolationMethod() != null ) {
683                ipm = gcr.getInterpolationMethod().value;
684            }
685    
686            // TODO
687            // handle range sets e.g. time
688            // note that elevation is now handled after the results are in (see the handleGetCoverageResponse method below)
689            StringBuffer sb = new StringBuffer( 1000 );
690            sb.append( "service=WCS&request=GetCoverage" );
691            sb.append( "&version=" ).append( version );
692            sb.append( "&COVERAGE=" ).append( lay );
693            sb.append( "&crs=" ).append( crs );
694            sb.append( "&response_crs=" ).append( crs );
695            sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' );
696            sb.append( bbox.getMin().getY() ).append( ',' ).append( bbox.getMax().getX() );
697            sb.append( ',' ).append( bbox.getMax().getY() );
698            sb.append( "&WIDTH=" ).append( request.getWidth() );
699            sb.append( "&HEIGHT=" ).append( request.getHeight() );
700            sb.append( "&FORMAT=" ).append( format );
701            sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm );
702            try {
703                IDGenerator idg = IDGenerator.getInstance();
704                gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() );
705            } catch ( WCSException e ) {
706                throw new InconsistentRequestException( e.getMessage() );
707            } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) {
708                throw new InconsistentRequestException( e.getMessage() );
709            }
710    
711            LOG.logDebug( "GetCoverage request: " + sb.toString() );
712    
713            return gcr;
714    
715        }
716    
717        /**
718         * 
719         * @param result
720         * @return the response objects
721         * @throws Exception
722         */
723        public Object handleResponse( Object result )
724                                throws Exception {
725    
726            Object theme = null;
727            if ( result instanceof ResultCoverage ) {
728                theme = handleGetCoverageResponse( (ResultCoverage) result );
729            } else if ( result instanceof FeatureResult ) {
730                theme = handleGetFeatureResponse( (FeatureResult) result );
731            } else if ( result instanceof GetMapResult ) {
732                theme = handleGetMapResponse( (GetMapResult) result );
733            } else {
734                String s = Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" );
735                if ( datasource.isFailOnException() ) {
736                    OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
737                    theme = exce;
738                }
739            }
740            return theme;
741        }
742    
743        /**
744         * replaces all pixels within the passed image having a color that is defined to be transparent within their
745         * datasource with a transparent color.
746         * 
747         * @param img
748         * @return modified image
749         */
750        private BufferedImage setTransparentColors( BufferedImage img ) {
751    
752            Color[] colors = null;
753            if ( datasource.getType() == AbstractDataSource.LOCALWCS ) {
754                LocalWCSDataSource ds = (LocalWCSDataSource) datasource;
755                colors = ds.getTransparentColors();
756            } else if ( datasource.getType() == AbstractDataSource.REMOTEWCS ) {
757                RemoteWCSDataSource ds = (RemoteWCSDataSource) datasource;
758                colors = ds.getTransparentColors();
759            } else if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) {
760                RemoteWMSDataSource ds = (RemoteWMSDataSource) datasource;
761                colors = ds.getTransparentColors();
762            }
763    
764            if ( colors != null && colors.length > 0 ) {
765    
766                int[] clrs = new int[colors.length];
767                for ( int i = 0; i < clrs.length; i++ ) {
768                    clrs[i] = colors[i].getRGB();
769                }
770    
771                if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) {
772                    // if the incoming image does not allow transparency
773                    // it must be copyed to a image of ARGB type
774                    BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
775                    Graphics g = tmp.getGraphics();
776                    g.drawImage( img, 0, 0, null );
777                    g.dispose();
778                    img = tmp;
779                }
780    
781                // TODO
782                // should be replaced by a JAI operation
783                int w = img.getWidth();
784                int h = img.getHeight();
785                for ( int i = 0; i < w; i++ ) {
786                    for ( int j = 0; j < h; j++ ) {
787                        int col = img.getRGB( i, j );
788                        if ( shouldBeTransparent( colors, col ) ) {
789                            img.setRGB( i, j, 0x00FFFFFF );
790                        }
791                    }
792                }
793    
794            }
795    
796            return img;
797        }
798    
799        /**
800         * @return true if the distance between the image color and at least of the colors to be truned to be transparent is
801         *         less than 3 in an int RGB cube
802         * 
803         * @param colors
804         * @param color
805         */
806        private boolean shouldBeTransparent( Color[] colors, int color ) {
807            Color c2 = new Color( color );
808            int r = c2.getRed();
809            int g = c2.getGreen();
810            int b = c2.getBlue();
811            for ( int i = 0; i < colors.length; i++ ) {
812                int r1 = colors[i].getRed();
813                int g1 = colors[i].getGreen();
814                int b1 = colors[i].getBlue();
815                if ( Math.sqrt( ( r1 - r ) * ( r1 - r ) + ( g1 - g ) * ( g1 - g ) + ( b1 - b ) * ( b1 - b ) ) < 3 ) {
816                    return true;
817                }
818            }
819            return false;
820        }
821    
822        /**
823         * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt>
824         * from it
825         * 
826         * @param response
827         * @return the response objects
828         * @throws Exception
829         */
830        private Object handleGetMapResponse( GetMapResult response )
831                                throws Exception {
832    
833            BufferedImage bi = (BufferedImage) response.getMap();
834    
835            bi = setTransparentColors( bi );
836            GridCoverage gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
837            org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc );
838            Theme theme = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
839            return theme;
840    
841        }
842    
843        /**
844         * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
845         * 
846         * @param response
847         * @return the response objects
848         * @throws Exception
849         */
850        private Object handleGetFeatureResponse( FeatureResult response )
851                                throws Exception {
852            FeatureCollection fc = null;
853    
854            Object o = response.getResponse();
855            if ( o instanceof FeatureCollection ) {
856                fc = (FeatureCollection) o;
857            } else {
858                throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) );
859            }
860            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
861                LOG.logDebug( "result: " + fc );
862                for ( int i = 0; i < fc.size(); ++i ) {
863                    outputGeometries( fc.getFeature( i ) );
864                }
865            }
866    
867            org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( layer.getName(), this.handler.getRequestCRS(),
868                                                                           fc );
869    
870            return MapFactory.createTheme( datasource.getName().getPrefixedName(), fl, new UserStyle[] { style } );
871        }
872    
873        private void outputGeometries( Feature feature ) {
874            if ( feature == null ) {
875                return;
876            }
877            FeatureType ft = feature.getFeatureType();
878            PropertyType[] propertyTypes = ft.getProperties();
879            for ( PropertyType pt : propertyTypes ) {
880                if ( pt.getType() == Types.FEATURE ) {
881                    FeatureProperty[] fp = feature.getProperties( pt.getName() );
882                    if ( fp != null ) {
883                        for ( int i = 0; i < fp.length; i++ ) {
884                            outputGeometries( (Feature) fp[i].getValue() );
885                        }
886                    }
887                } else if ( pt.getType() == Types.GEOMETRY ) {
888                    Geometry g = feature.getDefaultGeometryPropertyValue();
889                    if ( g != null ) {
890                        try {
891                            LOG.logDebug( "geometrie: " + WKTAdapter.export( g ).toString() );
892                        } catch ( GeometryException e ) {
893                            LOG.logDebug( "Geometry couldn't be converted to Well Known Text: " + g );
894                        }
895                    }
896                }
897            }
898        }
899    
900        /**
901         * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
902         * 
903         * @param response
904         * @return the response objects
905         * @throws Exception
906         */
907        private Object handleGetCoverageResponse( ResultCoverage response )
908                                throws Exception {
909            ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
910            Object ro = null;
911            if ( gc != null ) {
912                BufferedImage bi = gc.getAsImage( -1, -1 );
913    
914                bi = setTransparentColors( bi );
915    
916                gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
917    
918                Dimension[] dims = handler.getConfiguration().getLayer( layer.getName() ).getDimension();
919                DimensionValues elev = request.getDimElev();
920                if ( dims != null ) {
921                    for ( Dimension dim : dims ) {
922                        if ( dim.getName().equalsIgnoreCase( "elevation" ) ) {
923                            if ( elev == null && dim.getDefaultValue() != null ) {
924                                request.setDimElev( new DimensionValues( dim.getDefaultValue() ) );
925                                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "="
926                                                            + dim.getDefaultValue() + " " + dim.getUnits() );
927                                elev = request.getDimElev();
928                            }
929                            if ( elev == null ) {
930                                InvalidParameterValueException towrap;
931                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE",
932                                                                                            dim.getName() ),
933                                                                             MISSINGDIMENSIONVALUE );
934                                throw new WMSExceptionFromWCS( towrap );
935                            }
936                            if ( !dim.isMultipleValues() && elev.hasMultipleValues() ) {
937                                InvalidParameterValueException towrap;
938                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES",
939                                                                                            dim.getName() ),
940                                                                             INVALIDDIMENSIONVALUE );
941                                throw new WMSExceptionFromWCS( towrap );
942                            }
943                            String singleValue = elev.values.peek().value;
944                            if ( elev.values.size() == 1 && singleValue != null ) {
945                                DimensionValues origValues = new DimensionValues( dim.getValues() );
946                                if ( !origValues.includesValue( singleValue ) ) {
947                                    if ( dim.isNearestValue() ) {
948                                        String nearestValue = origValues.getNearestValue( singleValue );
949                                        elev.values.peek().value = nearestValue;
950                                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "="
951                                                                    + nearestValue + " " + dim.getUnits() );
952                                    }
953                                }
954                            }
955    
956                            if ( !new DimensionValues( dim.getValues() ).includes( elev ) ) {
957                                InvalidParameterValueException towrap;
958                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_BAD_DIMENSION_VALUE" ),
959                                                                             INVALIDDIMENSIONVALUE );
960                                throw new WMSExceptionFromWCS( towrap );
961                            }
962                        }
963                    }
964                }
965                org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc, request );
966    
967                ro = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
968            } else {
969                throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE",
970                                                                                             datasource.getName() ) );
971            }
972            return ro;
973        }
974    
975        class WMSExceptionFromWCS extends Exception {
976    
977            private static final long serialVersionUID = 8999003296940731523L;
978    
979            OGCWebServiceException wrapped;
980    
981            WMSExceptionFromWCS( OGCWebServiceException towrap ) {
982                wrapped = towrap;
983            }
984    
985        }
986    
987    }