    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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: 22342 $
    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: 22342 $, $Date: 2010-02-02 16:58:18 +0100 (Di, 02. Feb 2010) $
    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            // please note that this code CANNOT BE UNCOMMENTED as it severely affects performance in many cases!
    372            for ( int j = 0; j < pp.size(); j++ ) {
    373                if ( !pp.get( j ).getAsString().endsWith( "$SCALE" ) ) {
    374                    sb.append( "<PropertyName>" ).append( pp.get( j ).getAsString() );
    375                    sb.append( "</PropertyName>" );
    376                }
    377            }
    378
    379            // add filters to list
    380            // BBOX filter
    381            StringBuffer sb2 = new StringBuffer( 512 );
    382            sb2.append( "<ogc:BBOX>" );
    383            sb2.append( "<PropertyName>" );
    384            sb2.append( ds.getGeometryProperty().getPrefixedName() );
    385            sb2.append( "</PropertyName>" );
    386            sb2.append( exportAsBox( bbox ) );
    387            sb2.append( "</ogc:BBOX>" );
    388            filters.add( sb2 );
    389            // custom filter for property value
    390            if ( useCustomFilter ) {
    391                sb2 = new StringBuffer( 512 );
    392                appendCustomFilter( sb2, filterProperty, filterValue );
    393                filters.add( sb2 );
    394            }
    395            // filters of query
    396            Query query = ds.getQuery();
    397            if ( query != null ) {
    398                Filter filter = query.getFilter();
    399                filters.addAll( extractFilters( filter ) );
    400                SortProperty[] sps = query.getSortProperties();
    401                if ( sps != null ) {
    402                    for ( SortProperty sp : sps ) {
    403                        sortProperties.add( extractSortProperty( sp ) );
    404                    }
    405                }
    406            }
    407            // filters from SLD
    408            if ( layer != null ) {
    409                LayerFeatureConstraints lfc = layer.getLayerFeatureConstraints();
    410                if ( lfc != null ) {
    411                    FeatureTypeConstraint[] fcs = lfc.getFeatureTypeConstraint();
    412                    if ( fcs != null ) {
    413                        for ( FeatureTypeConstraint fc : fcs ) {
    414                            filters.addAll( extractFilters( fc.getFilter() ) );
    415                        }
    416                    }
    417                }
    418            }
    419
    420            // filters from the dimensions
    421            Layer lay = handler.getConfiguration().getLayer( layer.getName() );
    422            Map<String, String> dimProps = ds.getDimProps();
    423            if ( lay.getDimension() != null && dimProps != null ) {
    424                for ( Dimension dim : lay.getDimension() ) {
    425                    if ( dim.getName().equals( "time" ) ) {
    426                        filters.add( handleDimension( request.getDimTime(), dim, dimProps.get( "time" ) ) );
    427                    }
    428                    if ( dim.getName().equals( "elevation" ) ) {
    429                        filters.add( handleDimension( request.getDimElev(), dim, dimProps.get( "elevation" ) ) );
    430                    }
    431                }
    432            }
    433
    434            // actually append the filters
    435            sb.append( "<ogc:Filter>" );
    436            if ( filters.size() > 1 ) {
    437                sb.append( "<ogc:And>" );
    438            }
    439            for ( StringBuffer s : filters ) {
    440                sb.append( s );
    441            }
    442            if ( filters.size() > 1 ) {
    443                sb.append( "</ogc:And>" );
    444            }
    445            sb.append( "</ogc:Filter>" );
    446            if ( sortProperties.size() > 0 ) {
    447                sb.append( "<ogc:SortBy>" );
    448                for ( StringBuffer s : sortProperties ) {
    449                    sb.append( s );
    450                }
    451                sb.append( "</ogc:SortBy>" );
    452            }
    453            sb.append( "</Query></GetFeature>" );
    454
    455            // create dom representation of the request
    456            Document doc = XMLTools.parse( new StringReader( sb.toString() ) );
    457
    458            if ( LOG.isDebug() ) {
    459                LOG.logDebug( "GetFeature request: "
    460                              + new XMLFragment( doc, "http://www.systemid.org" ).getAsPrettyString() );
    461            }
    462
    463            // create OGCWebServiceEvent object
    464            IDGenerator idg = IDGenerator.getInstance();
    465            GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
    466
    467            return gfr;
    468        }
    469
    470        private StringBuffer handleDimension( DimensionValues values, Dimension dim, String dimProp )
    471                                throws OGCWebServiceException {
    472            if ( values == null && dim.getDefaultValue() != null ) {
    473                values = new DimensionValues( dim.getDefaultValue() );
    474                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" + dim.getDefaultValue() + " "
    475                                            + dim.getUnits() );
    476            }
    477
    478            if ( values == null ) {
    479                throw new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", dim.getName() ),
    480                                                          MISSINGDIMENSIONVALUE );
    481            }
    482            if ( !dim.isMultipleValues() && values.hasMultipleValues() ) {
    483                throw new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", dim.getName() ),
    484                                                          INVALIDDIMENSIONVALUE );
    485            }
    486
    487            String singleValue = values.values.peek().value;
    488            if ( values.values.size() == 1 && singleValue != null ) {
    489                DimensionValues origValues = new DimensionValues( dim.getValues() );
    490                if ( !origValues.includesValue( singleValue ) ) {
    491                    if ( dim.isNearestValue() ) {
    492                        String nearestValue = origValues.getNearestValue( singleValue );
    493                        values.values.peek().value = nearestValue;
    494                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" + nearestValue + " "
    495                                                    + dim.getUnits() );
    496                    }
    497                }
    498            }
    499
    500            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
    501
    502            for ( DimensionValue val : values.values ) {
    503                StringBuffer sb = new StringBuffer( 512 );
    504                if ( val.value != null ) {
    505                    sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ).append( dimProp );
    506                    sb.append( "</ogc:PropertyName><ogc:Literal>" ).append( val.value );
    507                    sb.append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
    508                } else {
    509                    sb.append( "<ogc:PropertyIsBetween><ogc:PropertyName>" ).append( dimProp );
    510                    sb.append( "</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>" ).append( val.low );
    511                    sb.append( "</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>" ).append( val.high );
    512                    sb.append( "</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween>" );
    513                }
    514                filters.add( sb );
    515            }
    516
    517            if ( filters.size() == 1 ) {
    518                return filters.poll();
    519            }
    520
    521            StringBuffer sb = new StringBuffer( 512 * filters.size() );
    522            sb.append( "<ogc:Or>" );
    523            while ( filters.size() > 0 ) {
    524                sb.append( filters.poll() );
    525            }
    526            sb.append( "</ogc:Or>" );
    527            return sb;
    528        }
    529
    530        private static StringBuffer extractSortProperty( SortProperty sp ) {
    531            StringBuffer sb = new StringBuffer();
    532            sb.append( "<ogc:SortProperty>" );
    533            sb.append( "<ogc:PropertyName>" );
    534            sb.append( sp.getSortProperty().toString() );
    535            sb.append( "</ogc:PropertyName>" );
    536            sb.append( "<ogc:SortOrder>" );
    537            sb.append( sp.getSortOrder() ? "ASC" : "DESC" );
    538            sb.append( "</ogc:SortOrder>" );
    539            sb.append( "</ogc:SortProperty>" );
    540
    541            return sb;
    542        }
    543
    544        private static LinkedList<StringBuffer> extractFilters( Filter filter ) {
    545            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
    546
    547            if ( filter instanceof ComplexFilter ) {
    548                filters.add( ( ( (ComplexFilter) filter ).getOperation() ).toXML() );
    549            }
    550            if ( filter instanceof FeatureFilter ) {
    551                ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
    552                for ( int i = 0; i < featureIds.size(); i++ ) {
    553                    FeatureId fid = featureIds.get( i );
    554                    filters.add( fid.toXML() );
    555                }
    556            }
    557
    558            return filters;
    559        }
    560
    561        private static void appendCustomFilter( StringBuffer sb, String name, String value ) {
    562            String[] vals = value.split( "," );
    563            if ( vals.length > 1 ) {
    564                sb.append( "<ogc:Or>" );
    565            }
    566            for ( String val : vals ) {
    567                sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" );
    568                sb.append( name ).append( "</ogc:PropertyName><ogc:Literal>" );
    569                sb.append( val ).append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
    570            }
    571            if ( vals.length > 1 ) {
    572                sb.append( "</ogc:Or>" );
    573            }
    574        }
    575
    576        /**
    577         * transforms the requested BBOX into the DefaultSRS of the assigned feature type
    578         *
    579         * @param ds
    580         * @return the envelope
    581         * @throws OGCWebServiceException
    582         * @throws CRSTransformationException
    583         * @throws UnknownCRSException
    584         */
    585        private Envelope transformBBOX( LocalWFSDataSource ds )
    586                                throws OGCWebServiceException, CRSTransformationException, UnknownCRSException {
    587            Envelope bbox = request.getBoundingBox();
    588            // transform request bounding box to the coordinate reference
    589            // system the WFS holds the data if requesting CRS and WFS-Data
    590            // crs are different
    591            OGCWebService service = ds.getOGCWebService();
    592            WFSCapabilities capa;
    593            if ( service instanceof RemoteWFService ) {
    594                RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService();
    595                capa = wfs.getWFSCapabilities();
    596            } else {
    597                WFService wfs = (WFService) service;
    598                capa = wfs.getCapabilities();
    599            }
    600            // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities();
    601            QualifiedName gn = ds.getName();
    602            WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
    603
    604            if ( ft == null ) {
    605                throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) );
    606            }
    607
    608            // enable different formatations of the crs encoding for GML geometries
    609            String GML_SRS = "http://www.opengis.net/gml/srs/";
    610            String old_gml_srs = ft.getDefaultSRS().toASCIIString();
    611            String old_srs;
    612            if ( old_gml_srs.startsWith( GML_SRS ) ) {
    613                old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase();
    614            } else {
    615                old_srs = old_gml_srs;
    616            }
    617
    618            String new_srs = request.getSrs();
    619            String new_gml_srs;
    620            if ( old_gml_srs.startsWith( GML_SRS ) ) {
    621                new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase();
    622            } else {
    623                new_gml_srs = new_srs;
    624            }
    625
    626            if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) {
    627                GeoTransformer transformer = new GeoTransformer( CRSFactory.create( old_srs ) );
    628                bbox = transformer.transform( bbox, this.handler.getRequestCRS() );
    629            }
    630            return bbox;
    631        }
    632
    633        /**
    634         * creates a getCoverage request considering the getMap request and the filterconditions defined in the submitted
    635         * <tt>DataSource</tt> object The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>.
    636         *
    637         * @param ds
    638         *            the datasource
    639         * @param request
    640         *            the GetMap operation
    641         * @return GetCoverage request object
    642         * @throws InconsistentRequestException
    643         */
    644        protected static GetCoverage createGetCoverageRequest( AbstractDataSource ds, GetMap request )
    645                                throws InconsistentRequestException {
    646
    647            Envelope bbox = request.getBoundingBox();
    648
    649            double xres = bbox.getWidth() / request.getWidth();
    650            double yres = bbox.getHeight() / request.getHeight();
    651
    652            WorldFile tmpWorldFile = new WorldFile( xres, yres, 0.0, 0.0, bbox, WorldFile.TYPE.OUTER );
    653            bbox = tmpWorldFile.getEnvelope( WorldFile.TYPE.CENTER );
    654
    655            GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest();
    656
    657            String crs = request.getSrs();
    658            // if (gcr != null && gcr.getDomainSubset().getRequestSRS() != null) {
    659            // crs = gcr.getDomainSubset().getRequestSRS().getCode();
    660            // }
    661            String format = request.getFormat();
    662            int pos = format.indexOf( '/' );
    663            if ( pos > -1 )
    664                format = format.substring( pos + 1, format.length() );
    665            if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) {
    666                format = gcr.getOutput().getFormat().getCode();
    667            }
    668            if ( format.indexOf( "svg" ) > -1 ) {
    669                format = "tiff";
    670            }
    671            if ( format.startsWith( "png" ) ) {
    672                format = "png";
    673            }
    674
    675            String version = "1.0.0";
    676            if ( gcr != null && gcr.getVersion() != null ) {
    677                version = gcr.getVersion();
    678            }
    679            String lay = ds.getName().getPrefixedName();
    680            if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) {
    681                lay = gcr.getSourceCoverage();
    682            }
    683            String ipm = null;
    684            if ( gcr != null && gcr.getInterpolationMethod() != null ) {
    685                ipm = gcr.getInterpolationMethod().value;
    686            }
    687
    688            // TODO
    689            // handle range sets e.g. time
    690            // note that elevation is now handled after the results are in (see the handleGetCoverageResponse method below)
    691            StringBuffer sb = new StringBuffer( 1000 );
    692            sb.append( "service=WCS&request=GetCoverage" );
    693            sb.append( "&version=" ).append( version );
    694            sb.append( "&COVERAGE=" ).append( lay );
    695            sb.append( "&crs=" ).append( crs );
    696            sb.append( "&response_crs=" ).append( crs );
    697            sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' );
    698            sb.append( bbox.getMin().getY() ).append( ',' ).append( bbox.getMax().getX() );
    699            sb.append( ',' ).append( bbox.getMax().getY() );
    700            sb.append( "&WIDTH=" ).append( request.getWidth() );
    701            sb.append( "&HEIGHT=" ).append( request.getHeight() );
    702            sb.append( "&FORMAT=" ).append( format );
    703            sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm );
    704            try {
    705                IDGenerator idg = IDGenerator.getInstance();
    706                gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() );
    707            } catch ( WCSException e ) {
    708                throw new InconsistentRequestException( e.getMessage() );
    709            } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) {
    710                throw new InconsistentRequestException( e.getMessage() );
    711            }
    712
    713            LOG.logDebug( "GetCoverage request: " + sb.toString() );
    714
    715            return gcr;
    716
    717        }
    718
    719        /**
    720         *
    721         * @param result
    722         * @return the response objects
    723         * @throws Exception
    724         */
    725        public Object handleResponse( Object result )
    726                                throws Exception {
    727
    728            Object theme = null;
    729            if ( result instanceof ResultCoverage ) {
    730                theme = handleGetCoverageResponse( (ResultCoverage) result );
    731            } else if ( result instanceof FeatureResult ) {
    732                theme = handleGetFeatureResponse( (FeatureResult) result );
    733            } else if ( result instanceof GetMapResult ) {
    734                theme = handleGetMapResponse( (GetMapResult) result );
    735            } else {
    736                String s = Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" );
    737                if ( datasource.isFailOnException() ) {
    738                    OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
    739                    theme = exce;
    740                }
    741            }
    742            return theme;
    743        }
    744
    745        /**
    746         * replaces all pixels within the passed image having a color that is defined to be transparent within their
    747         * datasource with a transparent color.
    748         *
    749         * @param img
    750         * @return modified image
    751         */
    752        private BufferedImage setTransparentColors( BufferedImage img ) {
    753
    754            Color[] colors = null;
    755            if ( datasource.getType() == AbstractDataSource.LOCALWCS ) {
    756                LocalWCSDataSource ds = (LocalWCSDataSource) datasource;
    757                colors = ds.getTransparentColors();
    758            } else if ( datasource.getType() == AbstractDataSource.REMOTEWCS ) {
    759                RemoteWCSDataSource ds = (RemoteWCSDataSource) datasource;
    760                colors = ds.getTransparentColors();
    761            } else if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) {
    762                RemoteWMSDataSource ds = (RemoteWMSDataSource) datasource;
    763                colors = ds.getTransparentColors();
    764            }
    765
    766            if ( colors != null && colors.length > 0 ) {
    767
    768                int[] clrs = new int[colors.length];
    769                for ( int i = 0; i < clrs.length; i++ ) {
    770                    clrs[i] = colors[i].getRGB();
    771                }
    772
    773                if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) {
    774                    // if the incoming image does not allow transparency
    775                    // it must be copyed to a image of ARGB type
    776                    BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
    777                    Graphics g = tmp.getGraphics();
    778                    g.drawImage( img, 0, 0, null );
    779                    g.dispose();
    780                    img = tmp;
    781                }
    782
    783                // TODO
    784                // should be replaced by a JAI operation
    785                int w = img.getWidth();
    786                int h = img.getHeight();
    787                for ( int i = 0; i < w; i++ ) {
    788                    for ( int j = 0; j < h; j++ ) {
    789                        int col = img.getRGB( i, j );
    790                        if ( shouldBeTransparent( colors, col ) ) {
    791                            img.setRGB( i, j, 0x00FFFFFF );
    792                        }
    793                    }
    794                }
    795
    796            }
    797
    798            return img;
    799        }
    800
    801        /**
    802         * @return true if the distance between the image color and at least of the colors to be truned to be transparent is
    803         *         less than 3 in an int RGB cube
    804         *
    805         * @param colors
    806         * @param color
    807         */
    808        private boolean shouldBeTransparent( Color[] colors, int color ) {
    809            Color c2 = new Color( color );
    810            int r = c2.getRed();
    811            int g = c2.getGreen();
    812            int b = c2.getBlue();
    813            for ( int i = 0; i < colors.length; i++ ) {
    814                int r1 = colors[i].getRed();
    815                int g1 = colors[i].getGreen();
    816                int b1 = colors[i].getBlue();
    817                if ( Math.sqrt( ( r1 - r ) * ( r1 - r ) + ( g1 - g ) * ( g1 - g ) + ( b1 - b ) * ( b1 - b ) ) < 3 ) {
    818                    return true;
    819                }
    820            }
    821            return false;
    822        }
    823
    824        /**
    825         * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt>
    826         * from it
    827         *
    828         * @param response
    829         * @return the response objects
    830         * @throws Exception
    831         */
    832        private Object handleGetMapResponse( GetMapResult response )
    833                                throws Exception {
    834
    835            BufferedImage bi = (BufferedImage) response.getMap();
    836
    837            bi = setTransparentColors( bi );
    838            GridCoverage gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
    839            org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc );
    840            Theme theme = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
    841            return theme;
    842
    843        }
    844
    845        /**
    846         * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
    847         *
    848         * @param response
    849         * @return the response objects
    850         * @throws Exception
    851         */
    852        private Object handleGetFeatureResponse( FeatureResult response )
    853                                throws Exception {
    854            FeatureCollection fc = null;
    855
    856            Object o = response.getResponse();
    857            if ( o instanceof FeatureCollection ) {
    858                fc = (FeatureCollection) o;
    859            } else {
    860                throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) );
    861            }
    862            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    863                LOG.logDebug( "result: " + fc );
    864                for ( int i = 0; i < fc.size(); ++i ) {
    865                    outputGeometries( fc.getFeature( i ) );
    866                }
    867            }
    868
    869            org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( layer.getName(), this.handler.getRequestCRS(),
    870                                                                           fc );
    871
    872            return MapFactory.createTheme( datasource.getName().getPrefixedName(), fl, new UserStyle[] { style } );
    873        }
    874
    875        private void outputGeometries( Feature feature ) {
    876            if ( feature == null ) {
    877                return;
    878            }
    879            FeatureType ft = feature.getFeatureType();
    880            PropertyType[] propertyTypes = ft.getProperties();
    881            for ( PropertyType pt : propertyTypes ) {
    882                if ( pt.getType() == Types.FEATURE ) {
    883                    FeatureProperty[] fp = feature.getProperties( pt.getName() );
    884                    if ( fp != null ) {
    885                        for ( int i = 0; i < fp.length; i++ ) {
    886                            outputGeometries( (Feature) fp[i].getValue() );
    887                        }
    888                    }
    889                } else if ( pt.getType() == Types.GEOMETRY ) {
    890                    Geometry g = feature.getDefaultGeometryPropertyValue();
    891                    if ( g != null ) {
    892                        try {
    893                            LOG.logDebug( "geometrie: " + WKTAdapter.export( g ).toString() );
    894                        } catch ( GeometryException e ) {
    895                            LOG.logDebug( "Geometry couldn't be converted to Well Known Text: " + g );
    896                        }
    897                    }
    898                }
    899            }
    900        }
    901
    902        /**
    903         * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
    904         *
    905         * @param response
    906         * @return the response objects
    907         * @throws Exception
    908         */
    909        private Object handleGetCoverageResponse( ResultCoverage response )
    910                                throws Exception {
    911            ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
    912            Object ro = null;
    913            if ( gc != null ) {
    914                BufferedImage bi = gc.getAsImage( -1, -1 );
    915
    916                bi = setTransparentColors( bi );
    917
    918                gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
    919
    920                Dimension[] dims = handler.getConfiguration().getLayer( layer.getName() ).getDimension();
    921                DimensionValues elev = request.getDimElev();
    922                if ( dims != null ) {
    923                    for ( Dimension dim : dims ) {
    924                        if ( dim.getName().equalsIgnoreCase( "elevation" ) ) {
    925                            if ( elev == null && dim.getDefaultValue() != null ) {
    926                                request.setDimElev( new DimensionValues( dim.getDefaultValue() ) );
    927                                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "="
    928                                                            + dim.getDefaultValue() + " " + dim.getUnits() );
    929                                elev = request.getDimElev();
    930                            }
    931                            if ( elev == null ) {
    932                                InvalidParameterValueException towrap;
    933                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE",
    934                                                                                            dim.getName() ),
    935                                                                             MISSINGDIMENSIONVALUE );
    936                                throw new WMSExceptionFromWCS( towrap );
    937                            }
    938                            if ( !dim.isMultipleValues() && elev.hasMultipleValues() ) {
    939                                InvalidParameterValueException towrap;
    940                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES",
    941                                                                                            dim.getName() ),
    942                                                                             INVALIDDIMENSIONVALUE );
    943                                throw new WMSExceptionFromWCS( towrap );
    944                            }
    945                            String singleValue = elev.values.peek().value;
    946                            if ( elev.values.size() == 1 && singleValue != null ) {
    947                                DimensionValues origValues = new DimensionValues( dim.getValues() );
    948                                if ( !origValues.includesValue( singleValue ) ) {
    949                                    if ( dim.isNearestValue() ) {
    950                                        String nearestValue = origValues.getNearestValue( singleValue );
    951                                        elev.values.peek().value = nearestValue;
    952                                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "="
    953                                                                    + nearestValue + " " + dim.getUnits() );
    954                                    }
    955                                }
    956                            }
    957
    958                            if ( !new DimensionValues( dim.getValues() ).includes( elev ) ) {
    959                                InvalidParameterValueException towrap;
    960                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_BAD_DIMENSION_VALUE" ),
    961                                                                             INVALIDDIMENSIONVALUE );
    962                                throw new WMSExceptionFromWCS( towrap );
    963                            }
    964                        }
    965                    }
    966                }
    967                org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc, request );
    968
    969                ro = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
    970            } else {
    971                throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE",
    972                                                                                             datasource.getName() ) );
    973            }
    974            return ro;
    975        }
    976
    977        class WMSExceptionFromWCS extends Exception {
    978
    979            private static final long serialVersionUID = 8999003296940731523L;
    980
    981            OGCWebServiceException wrapped;
    982
    983            WMSExceptionFromWCS( OGCWebServiceException towrap ) {
    984                wrapped = towrap;
    985            }
    986
    987        }
    988
    989    }