    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wms/DefaultGetMapHandler.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036    package org.deegree.ogcwebservices.wms;
    037
    038    import static java.awt.image.BufferedImage.TYPE_BYTE_INDEXED;
    039    import static java.util.Arrays.asList;
    040    import static java.util.regex.Pattern.compile;
    041    import static javax.media.jai.operator.ColorQuantizerDescriptor.MEDIANCUT;
    042    import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84;
    043    import static org.deegree.framework.util.CollectionUtils.find;
    044    import static org.deegree.i18n.Messages.get;
    045    import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetMapResponse;
    046
    047    import java.awt.Color;
    048    import java.awt.Font;
    049    import java.awt.Graphics;
    050    import java.awt.Graphics2D;
    051    import java.awt.RenderingHints;
    052    import java.awt.image.BufferedImage;
    053    import java.awt.image.IndexColorModel;
    054    import java.awt.image.WritableRaster;
    055    import java.util.ArrayList;
    056    import java.util.HashMap;
    057    import java.util.LinkedList;
    058    import java.util.List;
    059    import java.util.concurrent.Callable;
    060    import java.util.concurrent.CancellationException;
    061    import java.util.regex.Pattern;
    062
    063    import javax.media.jai.RenderedOp;
    064    import javax.media.jai.operator.BandSelectDescriptor;
    065    import javax.media.jai.operator.ColorQuantizerDescriptor;
    066
    067    import org.apache.batik.svggen.SVGGraphics2D;
    068    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
    069    import org.deegree.framework.concurrent.Executor;
    070    import org.deegree.framework.log.ILogger;
    071    import org.deegree.framework.log.LoggerFactory;
    072    import org.deegree.framework.util.ImageUtils;
    073    import org.deegree.framework.util.MapUtils;
    074    import org.deegree.framework.util.MimeTypeMapper;
    075    import org.deegree.framework.util.CollectionUtils.Predicate;
    076    import org.deegree.graphics.MapFactory;
    077    import org.deegree.graphics.Theme;
    078    import org.deegree.graphics.optimizers.LabelOptimizer;
    079    import org.deegree.graphics.sld.AbstractLayer;
    080    import org.deegree.graphics.sld.AbstractStyle;
    081    import org.deegree.graphics.sld.NamedLayer;
    082    import org.deegree.graphics.sld.NamedStyle;
    083    import org.deegree.graphics.sld.StyledLayerDescriptor;
    084    import org.deegree.graphics.sld.UserLayer;
    085    import org.deegree.graphics.sld.UserStyle;
    086    import org.deegree.i18n.Messages;
    087    import org.deegree.model.crs.CRSFactory;
    088    import org.deegree.model.crs.CoordinateSystem;
    089    import org.deegree.model.crs.GeoTransformer;
    090    import org.deegree.model.spatialschema.Envelope;
    091    import org.deegree.model.spatialschema.Geometry;
    092    import org.deegree.model.spatialschema.GeometryFactory;
    093    import org.deegree.ogcbase.InvalidSRSException;
    094    import org.deegree.ogcwebservices.InconsistentRequestException;
    095    import org.deegree.ogcwebservices.InvalidParameterValueException;
    096    import org.deegree.ogcwebservices.OGCWebServiceException;
    097    import org.deegree.ogcwebservices.OGCWebServiceResponse;
    098    import org.deegree.ogcwebservices.wms.GetMapServiceInvokerForNL.WMSExceptionFromWCS;
    099    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
    100    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
    101    import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
    102    import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
    103    import org.deegree.ogcwebservices.wms.configuration.WMSConfiguration_1_3_0;
    104    import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams;
    105    import org.deegree.ogcwebservices.wms.operation.GetMap;
    106    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
    107    import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
    108    import org.deegree.ogcwebservices.wms.operation.GetMap.Layer;
    109    import org.w3c.dom.Element;
    110
    111    /**
    112     *
    113     *
    114     * @version $Revision: 18195 $
    115     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    116     */
    117    public class DefaultGetMapHandler implements GetMapHandler {
    118
    119        private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
    120
    121        private GetMap request = null;
    122
    123        // private Object[] themes = null;
    124
    125        private double scale = 0;
    126
    127        private CoordinateSystem reqCRS = null;
    128
    129        private WMSConfigurationType configuration = null;
    130
    131        private BufferedImage copyrightImg = null;
    132
    133        boolean version130 = false;
    134
    135        HashMap<String, String> sqls;
    136
    137        private GeoTransformer transformToWGS84;
    138
    139        // could be improved, I'm sure
    140        private static final Pattern CHECKSQL = compile( "(^insert|^update|^delete|.*[ ()]insert[() ]|.*[() ]update[() ]|.*[() ]delete[() ]).*" );
    141
    142        /**
    143         * Creates a new GetMapHandler object.
    144         *
    145         * @param configuration
    146         * @param request
    147         *            request to perform
    148         */
    149        public DefaultGetMapHandler( WMSConfigurationType configuration, GetMap request ) {
    150            this.request = request;
    151            this.configuration = configuration;
    152
    153            try {
    154                // get copyright image if possible
    155                copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyRight() );
    156            } catch ( Exception e ) {
    157                // don't use copyright
    158            }
    159
    160        }
    161
    162        /**
    163         * returns the configuration used by the handler
    164         *
    165         * @return the configuration document
    166         */
    167        public WMSConfigurationType getConfiguration() {
    168            return configuration;
    169        }
    170
    171        /**
    172         * performs a GetMap request and returns the result encapsulated within a <tt>GetMapResult</tt> object.
    173         * <p>
    174         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
    175         * impossible to return a result. If something went wrong performing the request (none fatal error) The exception
    176         * shall be encapsulated within the response object to be returned to the client as requested (GetMap-Request
    177         * EXCEPTION-Parameter).
    178         *
    179         * @return response to the GetMap response
    180         */
    181        public OGCWebServiceResponse performGetMap()
    182                                throws OGCWebServiceException {
    183
    184            // get templates, check templates
    185            String sqltemplates = request.getVendorSpecificParameter( "SQLTEMPLATES" );
    186            if ( sqltemplates != null ) {
    187                LinkedList<String> sqls = new LinkedList<String>();
    188                sqls.addAll( asList( sqltemplates.split( ";" ) ) );
    189                if ( sqls.size() != request.getLayers().length ) {
    190                    throw new InvalidParameterValueException( get( "WMS_INVALID_SQL_TEMPLATE_NUMBER" ) );
    191                }
    192                this.sqls = new HashMap<String, String>( sqls.size() );
    193                for ( int i = 0; i < request.getLayers().length; ++i ) {
    194                    String sql = sqls.peek();
    195                    if ( !sql.equals( "default" ) ) {
    196                        if ( CHECKSQL.matcher( sql.toLowerCase() ).matches() ) {
    197                            throw new InvalidParameterValueException(
    198                                                                      get( "WMS_INVALID_SQL_TEMPLATE_NO_TRANSACTION_PLEASE" ) );
    199                        }
    200                        String name = request.getLayers()[i].getName();
    201                        // ok iff all database data sources have custom sql allowed
    202                        for ( AbstractDataSource ds : configuration.getLayer( name ).getDataSource() ) {
    203                            if ( ds instanceof DatabaseDataSource ) {
    204                                if ( !( (DatabaseDataSource) ds ).isCustomSQLAllowed() ) {
    205                                    throw new InvalidParameterValueException(
    206                                                                              get(
    207                                                                                   "WMS_SQL_TEMPLATE_NOT_ALLOWED_FOR_LAYER",
    208                                                                                   name ) );
    209                                }
    210                            }
    211                        }
    212
    213                        this.sqls.put( name, sqls.poll() );
    214                    }
    215                }
    216            }
    217
    218            List<Callable<Object>> themes = constructThemes();
    219
    220            Executor executor = Executor.getInstance();
    221            try {
    222                List<ExecutionFinishedEvent<Object>> results;
    223                results = executor.performSynchronously( themes, configuration.getDeegreeParams().getRequestTimeLimit() );
    224
    225                GetMapResult res = renderMap( results );
    226                return res;
    227            } catch ( InterruptedException e ) {
    228                LOG.logError( e.getMessage(), e );
    229                String s = Messages.getMessage( "WMS_WAITING" );
    230                throw new OGCWebServiceException( getClass().getName(), s );
    231            }
    232        }
    233
    234        /**
    235         * @return a list of callables that construct the maps to be painted
    236         * @throws OGCWebServiceException
    237         */
    238        public List<Callable<Object>> constructThemes()
    239                                throws OGCWebServiceException {
    240            // some initialization is done here because the constructor is called by reflection
    241            // and the exceptions won't be properly handled in that case
    242            if ( reqCRS == null ) {
    243                try {
    244                    reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
    245                } catch ( Exception e ) {
    246                    throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
    247                }
    248            }
    249
    250            version130 = "1.3.0".equals( request.getVersion() );
    251
    252            // exceeds the max allowed map width ?
    253            int maxWidth = configuration.getDeegreeParams().getMaxMapWidth();
    254            if ( ( maxWidth != 0 ) && ( request.getWidth() > maxWidth ) ) {
    255                throw new InconsistentRequestException( Messages.getMessage( "WMS_EXCEEDS_WIDTH", new Integer( maxWidth ) ) );
    256            }
    257
    258            // exceeds the max allowed map height ?
    259            int maxHeight = configuration.getDeegreeParams().getMaxMapHeight();
    260            if ( ( maxHeight != 0 ) && ( request.getHeight() > maxHeight ) ) {
    261                throw new InconsistentRequestException(
    262                                                        Messages.getMessage( "WMS_EXCEEDS_HEIGHT", new Integer( maxHeight ) ) );
    263            }
    264
    265            try {
    266                double pixelSize = 1;
    267                if ( version130 ) {
    268                    // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
    269                    // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
    270                    pixelSize = MapUtils.DEFAULT_PIXEL_SIZE;
    271                }
    272
    273                scale = MapUtils.calcScale( request.getWidth(), request.getHeight(), request.getBoundingBox(), reqCRS,
    274                                            pixelSize );
    275
    276                LOG.logInfo( "OGC WMS scale: " + scale );
    277            } catch ( Exception e ) {
    278                LOG.logError( e.getMessage(), e );
    279                throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC" ) );
    280            }
    281
    282            Layer[] ls = request.getLayers();
    283
    284            // if 1.3.0, check for maximum allowed layers
    285            if ( version130 ) {
    286                WMSConfiguration_1_3_0 cfg = (WMSConfiguration_1_3_0) configuration;
    287                if ( ls.length > cfg.getLayerLimit() ) {
    288                    String ms = Messages.getMessage( "WMS_EXCEEDS_NUMBER", new Integer( cfg.getLayerLimit() ) );
    289                    throw new InconsistentRequestException( ms );
    290                }
    291            }
    292
    293            Layer[] oldLayers = ls;
    294            ls = validateLayers( ls );
    295
    296            LOG.logDebug( "Validated " + ls.length + " layers." );
    297
    298            StyledLayerDescriptor sld = toSLD( oldLayers, request.getStyledLayerDescriptor() );
    299
    300            AbstractLayer[] layers = sld.getLayers();
    301
    302            LOG.logDebug( "After SLD consideration, found " + layers.length + " layers." );
    303
    304            Envelope wgs84bbox = request.getBoundingBox();
    305            if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
    306                // transform the bounding box of the request to EPSG:4326
    307                transformToWGS84 = new GeoTransformer( CRSFactory.create( WGS84 ) );
    308                try {
    309                    wgs84bbox = transformToWGS84.transform( wgs84bbox, reqCRS );
    310                } catch ( Exception e ) {
    311                    // should never happen
    312                    LOG.logError( "Could not validate WMS datasource area", e );
    313                }
    314
    315            }
    316
    317            List<Callable<Object>> themes = new LinkedList<Callable<Object>>();
    318            for ( int i = 0; i < layers.length; i++ ) {
    319
    320                if ( layers[i] instanceof NamedLayer ) {
    321                    String styleName = null;
    322                    if ( i < request.getLayers().length ) {
    323                        styleName = request.getLayers()[i].getStyleName();
    324                    }
    325                    invokeNamedLayer( layers[i], styleName, themes, wgs84bbox );
    326                } else {
    327                    double sc = scale;
    328                    if ( !version130 ) {
    329                        // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
    330                        // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
    331                        sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
    332                    }
    333                    themes.add( new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], sc ) );
    334                }
    335            }
    336
    337            return themes;
    338        }
    339
    340        /**
    341         * this methods validates layer in two ways:<br>
    342         * a) are layers available from the current WMS<br>
    343         * b) If a layer is selected that includes other layers determine all its sublayers having <Name>s and return them
    344         * instead
    345         *
    346         * @param ls
    347         * @return the layers
    348         * @throws LayerNotDefinedException
    349         * @throws InvalidSRSException
    350         */
    351        private Layer[] validateLayers( Layer[] ls )
    352                                throws LayerNotDefinedException, InvalidSRSException {
    353
    354            List<Layer> layer = new ArrayList<Layer>( ls.length );
    355            for ( int i = 0; i < ls.length; i++ ) {
    356                org.deegree.ogcwebservices.wms.capabilities.Layer l = configuration.getLayer( ls[i].getName() );
    357
    358                if ( l == null ) {
    359                    throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", ls[i].getName() ) );
    360                }
    361
    362                validateSRS( l.getSrs(), ls[i].getName() );
    363
    364                layer.add( ls[i] );
    365                if ( l.getLayer() != null ) {
    366                    layer = addNestedLayers( l.getLayer(), ls[i].getStyleName(), layer );
    367                }
    368            }
    369
    370            return layer.toArray( new Layer[layer.size()] );
    371        }
    372
    373        /**
    374         * adds all direct and none direct sub-layers of the passed WMS capabilities layer as
    375         *
    376         * @see GetMap.Layer to the passed list.
    377         * @param list
    378         * @return all sublayers
    379         * @throws InvalidSRSException
    380         */
    381        private List<Layer> addNestedLayers( org.deegree.ogcwebservices.wms.capabilities.Layer[] ll, String styleName,
    382                                             List<Layer> list )
    383                                throws InvalidSRSException {
    384
    385            for ( int j = 0; j < ll.length; j++ ) {
    386                if ( ll[j].getName() != null ) {
    387                    String name = ll[j].getName();
    388                    validateSRS( ll[j].getSrs(), name );
    389                    list.add( GetMap.createLayer( name, styleName ) );
    390                }
    391                if ( ll[j].getLayer() != null ) {
    392                    list = addNestedLayers( ll[j].getLayer(), styleName, list );
    393                }
    394
    395            }
    396            return list;
    397        }
    398
    399        /**
    400         * throws an exception if the requested SRS is not be supported by the passed layer (name)
    401         *
    402         * @param srs
    403         * @param name
    404         * @throws InvalidSRSException
    405         */
    406        private void validateSRS( String[] srs, String name )
    407                                throws InvalidSRSException {
    408            boolean validSRS = false;
    409            for ( int k = 0; k < srs.length; k++ ) {
    410                validSRS = srs[k].equalsIgnoreCase( reqCRS.getIdentifier() );
    411                if ( validSRS )
    412                    break;
    413            }
    414            if ( !validSRS ) {
    415                String s = Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", reqCRS.getIdentifier(), name );
    416                throw new InvalidSRSException( s );
    417            }
    418        }
    419
    420        private void invokeNamedLayer( AbstractLayer layer, String styleName, List<Callable<Object>> tasks,
    421                                       Envelope wgs84bbox )
    422                                throws OGCWebServiceException {
    423            org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layer.getName() );
    424
    425            LOG.logDebug( "Invoked layer " + layer.getName() );
    426            if ( validate( lay, layer.getName(), wgs84bbox ) ) {
    427                UserStyle us = getStyles( (NamedLayer) layer, styleName );
    428                AbstractDataSource[] ds = lay.getDataSource();
    429
    430                if ( ds.length == 0 ) {
    431                    LOG.logDebug( "No datasources for layer " + layer.getName() );
    432                } else {
    433                    for ( int j = 0; j < ds.length; j++ ) {
    434
    435                        LOG.logDebug( "Invoked datasource " + ds[j].getClass() + " for layer " + layer.getName() );
    436
    437                        ScaleHint scaleHint = ds[j].getScaleHint();
    438                        if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax()
    439                             && isValidArea( ds[j].getValidArea() ) ) {
    440                            double sc = scale;
    441                            if ( !version130 ) {
    442                                // required because for WMS 1.3.0 'scale' represents the
    443                                // ScaleDenominator
    444                                // and for WMS < 1.3.0 it represents the size of a pixel diagonal in
    445                                // meter
    446                                sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
    447                            }
    448                            GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, (NamedLayer) layer, ds[j],
    449                                                                                          us, sc );
    450                            tasks.add( si );
    451                        } else {
    452                            LOG.logDebug( "Not showing layer " + layer.getName() + " due to scale" );
    453                        }
    454                    }
    455                }
    456            } else {
    457                // using side effects for everything is great:
    458                // when layers are eg. out of the bounding box, the use of invalid styles was not checked
    459                // so let's do it here...
    460                getStyles( (NamedLayer) layer, styleName );
    461            }
    462        }
    463
    464        /**
    465         * returns true if the requested boundingbox intersects with the valid area of a datasource
    466         *
    467         * @param validArea
    468         */
    469        private boolean isValidArea( Geometry validArea ) {
    470
    471            if ( validArea != null ) {
    472                try {
    473                    Envelope env = request.getBoundingBox();
    474                    Geometry geom = GeometryFactory.createSurface( env, reqCRS );
    475                    if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
    476                        // if requested CRS is not identical to the CRS of the valid area
    477                        // a transformation must be performed before intersection can
    478                        // be checked
    479                        GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
    480                        geom = gt.transform( geom );
    481                    }
    482                    return geom.intersects( validArea );
    483                } catch ( Exception e ) {
    484                    // should never happen
    485                    LOG.logError( "Could not validate WMS datasource area", e );
    486                }
    487            }
    488            return true;
    489        }
    490
    491        /**
    492         * creates a StyledLayerDocument containing all requested layer, nested layers if required and assigend styles. Not
    493         * considered are nested layers for mixed requests (LAYERS- and SLD(_BODY)- parameter has been defined)
    494         *
    495         * @param layers
    496         * @param inSLD
    497         * @return a combined SLD object
    498         * @throws InvalidSRSException
    499         */
    500        private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
    501                                throws InvalidSRSException {
    502            StyledLayerDescriptor sld = null;
    503
    504            if ( layers != null && layers.length > 0 && inSLD == null ) {
    505                // if just a list of layers has been requested
    506
    507                // create a SLD from the requested LAYERS and assigned STYLES
    508                List<AbstractLayer> al = new ArrayList<AbstractLayer>( layers.length * 2 );
    509                for ( int i = 0; i < layers.length; i++ ) {
    510                    AbstractStyle[] as = new AbstractStyle[] { new NamedStyle( layers[i].getStyleName() ) };
    511                    al.add( new NamedLayer( layers[i].getName(), null, as ) );
    512
    513                    // collect all named nested layers
    514                    org.deegree.ogcwebservices.wms.capabilities.Layer lla;
    515                    lla = configuration.getLayer( layers[i].getName() );
    516                    List<GetMap.Layer> list = new ArrayList<GetMap.Layer>();
    517                    addNestedLayers( lla.getLayer(), layers[i].getStyleName(), list );
    518
    519                    // add nested layers to list of layers to be handled
    520                    for ( int j = 0; j < list.size(); j++ ) {
    521                        GetMap.Layer nestedLayer = list.get( j );
    522                        as = new AbstractStyle[] { new NamedStyle( nestedLayer.getStyleName() ) };
    523                        al.add( new NamedLayer( nestedLayer.getName(), null, as ) );
    524                    }
    525                }
    526                sld = new StyledLayerDescriptor( al.toArray( new AbstractLayer[al.size()] ), "1.0.0" );
    527            } else if ( layers != null && layers.length > 0 && inSLD != null ) {
    528                // if layers not null and sld is not null then SLD layers just be
    529                // considered if present in the layers list
    530                // TODO
    531                // layer with nested layers are not handled correctly and I think
    532                // it really causes a lot of problems to use them in such a way
    533                // because the style assigned to the mesting layer must be
    534                // applicable for all nested layers.
    535                List<String> list = new ArrayList<String>();
    536                for ( int i = 0; i < layers.length; i++ ) {
    537                    list.add( layers[i].getName() );
    538                }
    539
    540                List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
    541
    542                for ( final GetMap.Layer lay : layers ) {
    543                    NamedLayer sldLay = find( inSLD.getNamedLayers(), new Predicate<NamedLayer>() {
    544                        public boolean eval( NamedLayer t ) {
    545                            return t.getName().equals( lay.getName() );
    546                        }
    547                    } );
    548
    549                    AbstractStyle[] as;
    550                    if ( sldLay == null ) {
    551                        as = new AbstractStyle[] { new NamedStyle( lay.getStyleName() ) };
    552                        newList.add( new NamedLayer( lay.getName(), null, as ) );
    553                    } else {
    554                        newList.add( sldLay );
    555                    }
    556
    557                    // finally, don't forget the user layers
    558                    for ( UserLayer ul : inSLD.getUserLayers() ) {
    559                        newList.add( ul );
    560                    }
    561                }
    562
    563                AbstractLayer[] al = new AbstractLayer[newList.size()];
    564                sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
    565            } else {
    566                // if no layers but a SLD is defined ...
    567                AbstractLayer[] as = inSLD.getLayers();
    568                for ( AbstractLayer l : as ) {
    569                    addNestedLayers( l, inSLD );
    570                }
    571
    572                sld = inSLD;
    573            }
    574
    575            return sld;
    576        }
    577
    578        // adds the nested layers to the sld
    579        private void addNestedLayers( AbstractLayer l, StyledLayerDescriptor sld ) {
    580            if ( !( l instanceof NamedLayer ) ) {
    581                return;
    582            }
    583            if ( configuration.getLayer( l.getName() ) == null ) {
    584                return;
    585            }
    586
    587            org.deegree.ogcwebservices.wms.capabilities.Layer[] ls;
    588            ls = configuration.getLayer( l.getName() ).getLayer();
    589            for ( org.deegree.ogcwebservices.wms.capabilities.Layer lay : ls ) {
    590                NamedStyle sty = new NamedStyle( lay.getStyles()[0].getName() );
    591                AbstractStyle[] newSty = new AbstractStyle[] { sty };
    592                NamedLayer newLay = new NamedLayer( lay.getName(), null, newSty );
    593                sld.addLayer( newLay );
    594            }
    595        }
    596
    597        /**
    598         * returns the <tt>UserStyle</tt>s assigned to a named layer
    599         *
    600         * @param sldLayer
    601         *            layer to get the styles for
    602         * @param styleName
    603         *            requested stylename (from the KVP encoding)
    604         */
    605        private UserStyle getStyles( NamedLayer sldLayer, String styleName )
    606                                throws OGCWebServiceException {
    607
    608            AbstractStyle[] styles = sldLayer.getStyles();
    609            UserStyle us = null;
    610
    611            // to avoid retrieving the layer again for each style
    612            org.deegree.ogcwebservices.wms.capabilities.Layer layer = null;
    613            layer = configuration.getLayer( sldLayer.getName() );
    614            int i = 0;
    615            while ( us == null && i < styles.length ) {
    616                if ( styles[i] instanceof NamedStyle ) {
    617                    // styles will be taken from the WMS's style repository
    618                    us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
    619                } else {
    620                    // if the requested style fits the name of the defined style or
    621                    // if the defined style is marked as default and the requested
    622                    // style if 'default' the condition is true. This includes that
    623                    // if more than one style with the same name or more than one
    624                    // style is marked as default always the first will be choosen
    625                    if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
    626                         || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
    627                        us = (UserStyle) styles[i];
    628                    }
    629                }
    630                i++;
    631            }
    632            if ( us == null ) {
    633                // this may happens if the SLD contains a named layer but not
    634                // a style! yes this is valid according to SLD spec 1.0.0
    635                us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
    636            }
    637            return us;
    638        }
    639
    640        /**
    641         *
    642         * @param styleName
    643         * @param layerName
    644         * @param layer
    645         * @return the style
    646         * @throws StyleNotDefinedException
    647         */
    648        public UserStyle getPredefinedStyle( String styleName, String layerName,
    649                                              org.deegree.ogcwebservices.wms.capabilities.Layer layer )
    650                                throws StyleNotDefinedException {
    651            UserStyle us = null;
    652            if ( "default".equals( styleName ) ) {
    653                us = layer.getStyle( styleName );
    654            }
    655
    656            if ( us == null ) {
    657                if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
    658                     || styleName.equals( "default" ) ) {
    659                    styleName = "default:" + layerName;
    660                }
    661            }
    662
    663            us = layer.getStyle( styleName );
    664
    665            if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
    666                String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
    667                throw new StyleNotDefinedException( s );
    668            }
    669            return us;
    670        }
    671
    672        /**
    673         * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
    674         * be thrown. If the layer matches the request, but isn't able to deviever data for the requested area and/or scale
    675         * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
    676         * true will be returned.
    677         *
    678         * @param layer
    679         *            layer as defined at the capabilities/configuration
    680         * @param name
    681         *            name of the layer (must be submitted separately because the layer parameter can be <tt>null</tt>
    682         * @param wgs84bbox
    683         *            the wgs84 bbox of the request
    684         */
    685        private boolean validate( org.deegree.ogcwebservices.wms.capabilities.Layer layer, String name, Envelope wgs84bbox )
    686                                throws OGCWebServiceException {
    687
    688            // check if layer is available
    689            if ( layer == null ) {
    690                throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", name ) );
    691            }
    692
    693            // check bounding box
    694            try {
    695                Envelope layerBbox = layer.getLatLonBoundingBox();
    696                if ( !wgs84bbox.intersects( layerBbox ) ) {
    697                    LOG.logDebug( "Not showing layer because the request is out of the bounding box." );
    698                    return false;
    699                }
    700
    701            } catch ( Exception e ) {
    702                LOG.logError( e.getMessage(), e );
    703                throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
    704            }
    705
    706            return true;
    707        }
    708
    709        /**
    710         * renders the map from the <tt>DisplayElement</tt>s
    711         *
    712         * @param results
    713         * @return a result object suitable for further processing
    714         *
    715         * @throws OGCWebServiceException
    716         */
    717        public GetMapResult renderMap( List<ExecutionFinishedEvent<Object>> results )
    718                                throws OGCWebServiceException {
    719
    720            OGCWebServiceException exce = null;
    721
    722            ArrayList<Object> list = new ArrayList<Object>( 50 );
    723            for ( ExecutionFinishedEvent<Object> evt : results ) {
    724                Object o = null;
    725
    726                // exception handling might be handled in a better way
    727                try {
    728                    o = evt.getResult();
    729                } catch ( CancellationException e ) {
    730                    exce = new OGCWebServiceException( getClass().getName(), e.toString() );
    731                } catch ( OGCWebServiceException e ) {
    732                    throw e;
    733                } catch ( Throwable e ) {
    734                    exce = new OGCWebServiceException( getClass().getName(), e.toString() );
    735                }
    736
    737                if ( o instanceof WMSExceptionFromWCS ) {
    738                    if ( results.size() == 1 ) {
    739                        exce = ( (WMSExceptionFromWCS) o ).wrapped;
    740                        o = exce;
    741                    } else {
    742                        o = null;
    743                    }
    744                }
    745                if ( o instanceof Exception ) {
    746                    exce = new OGCWebServiceException( getClass().getName(), o.toString() );
    747                }
    748                if ( o instanceof OGCWebServiceException ) {
    749                    exce = (OGCWebServiceException) o;
    750                    break;
    751                }
    752                if ( o != null ) {
    753                    list.add( o );
    754                }
    755            }
    756
    757            return render( list.toArray( new Theme[list.size()] ), exce );
    758        }
    759
    760        /**
    761         * @param themes
    762         * @param exce
    763         * @return a result object suitable for further processing elsewhere
    764         * @throws InvalidSRSException
    765         */
    766        public GetMapResult render( Theme[] themes, OGCWebServiceException exce )
    767                                throws InvalidSRSException {
    768            // some initialization is done here because the constructor is called by reflection
    769            // and the exceptions won't be properly handled in that case
    770            // NOTE that it has to be repeated here in case someone wants to use this method only!
    771            if ( reqCRS == null ) {
    772                try {
    773                    reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
    774                } catch ( Exception e ) {
    775                    throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
    776                }
    777            }
    778
    779            GetMapResult response = null;
    780
    781            String mime = MimeTypeMapper.toMimeType( request.getFormat() );
    782
    783            if ( configuration.getDeegreeParams().getDefaultPNGFormat() != null && mime.equalsIgnoreCase( "image/png" ) ) {
    784                mime = configuration.getDeegreeParams().getDefaultPNGFormat();
    785            }
    786
    787            // get target object for rendering
    788            Object target = GraphicContextFactory.createGraphicTarget( mime, request.getWidth(), request.getHeight() );
    789
    790            // get graphic context of the target
    791            Graphics g = GraphicContextFactory.createGraphicContext( mime, target );
    792            if ( exce == null ) {
    793                // only if no exception occured
    794                try {
    795                    org.deegree.graphics.MapView map = null;
    796                    if ( themes.length > 0 ) {
    797                        map = MapFactory.createMapView( "deegree WMS", request.getBoundingBox(), reqCRS, themes,
    798                                                        MapUtils.DEFAULT_PIXEL_SIZE );
    799                    }
    800                    g.setClip( 0, 0, request.getWidth(), request.getHeight() );
    801
    802                    if ( !request.getTransparency() ) {
    803                        if ( g instanceof Graphics2D ) {
    804                            // this ensures real clearing (rendering modifies the color ever so
    805                            // slightly)
    806                            ( (Graphics2D) g ).setBackground( request.getBGColor() );
    807                            g.clearRect( 0, 0, request.getWidth(), request.getHeight() );
    808                        } else {
    809                            g.setColor( request.getBGColor() );
    810                            g.fillRect( 0, 0, request.getWidth(), request.getHeight() );
    811                        }
    812                    }
    813
    814                    if ( map != null ) {
    815                        Theme[] thms = map.getAllThemes();
    816                        map.addOptimizer( new LabelOptimizer( thms ) );
    817                        // antialiasing must be switched of for gif output format
    818                        // because the antialiasing may create more than 255 colors
    819                        // in the map/image, even just a few colors are defined in
    820                        // the styles
    821                        if ( !request.getFormat().equalsIgnoreCase( "image/gif" ) ) {
    822                            if ( configuration.getDeegreeParams().isAntiAliased() ) {
    823                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
    824                                                                     RenderingHints.VALUE_ANTIALIAS_ON );
    825                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
    826                                                                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
    827                            }
    828                        }
    829                        map.paint( g );
    830                    }
    831                } catch ( Exception e ) {
    832                    LOG.logError( e.getMessage(), e );
    833                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
    834                }
    835            }
    836
    837            // print a copyright note at the left lower corner of the map
    838            printCopyright( g, request.getHeight() );
    839
    840            if ( mime.equals( "image/svg+xml" ) || mime.equals( "image/svg xml" ) ) {
    841                Element root = ( (SVGGraphics2D) g ).getRoot();
    842                root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
    843                response = WMSProtocolFactory.createGetMapResponse( request, exce, root );
    844            } else {
    845
    846                BufferedImage img = (BufferedImage) target;
    847
    848                if ( mime.equals( "image/png; mode=8bit" ) ) {
    849                    RenderedOp torgb = BandSelectDescriptor.create( img, new int[] { 0, 1, 2 }, null );
    850
    851                    torgb = ColorQuantizerDescriptor.create( torgb, MEDIANCUT, 254, null, null, null, null, null );
    852
    853                    WritableRaster data = torgb.getAsBufferedImage().getRaster();
    854
    855                    IndexColorModel model = (IndexColorModel) torgb.getColorModel();
    856                    byte[] reds = new byte[256];
    857                    byte[] greens = new byte[256];
    858                    byte[] blues = new byte[256];
    859                    byte[] alphas = new byte[256];
    860                    model.getReds( reds );
    861                    model.getGreens( greens );
    862                    model.getBlues( blues );
    863                    // note that this COULD BE OPTIMIZED to SUPPORT EG HALF TRANSPARENT PIXELS for PNG-8!
    864                    // It's not true that PNG-8 does not support this! Try setting the value to eg. 128 here and see what
    865                    // you'll get...
    866                    for ( int i = 0; i < 254; ++i ) {
    867                        alphas[i] = -1;
    868                    }
    869                    alphas[255] = 0;
    870                    IndexColorModel newModel = new IndexColorModel( 8, 256, reds, greens, blues, alphas );
    871
    872                    // yeah, double memory, but it was the only way I could find (I could be blind...)
    873                    BufferedImage res = new BufferedImage( torgb.getWidth(), torgb.getHeight(), TYPE_BYTE_INDEXED, newModel );
    874                    res.setData( data );
    875
    876                    // do it the hard way as the OR operation would destroy the channels
    877                    for ( int y = 0; y < img.getHeight(); ++y ) {
    878                        for ( int x = 0; x < img.getWidth(); ++x ) {
    879                            if ( img.getRGB( x, y ) == 0 ) {
    880                                res.setRGB( x, y, 0 );
    881                            }
    882                        }
    883                    }
    884
    885                    target = res;
    886                }
    887
    888                response = createGetMapResponse( request, exce, target );
    889            }
    890            g.dispose();
    891
    892            return response;
    893        }
    894
    895        // works, but only with some bogus pixel that means "transparency"
    896        // private static BufferedImage makeTransparent( BufferedImage img ) {
    897        // IndexColorModel cm = (IndexColorModel) img.getColorModel();
    898        // WritableRaster raster = img.getRaster();
    899        // int pixel = raster.getSample( 0, 0, 0 );
    900        // int size = cm.getMapSize();
    901        // byte[] reds = new byte[size];
    902        // byte[] greens = new byte[size];
    903        // byte[] blues = new byte[size];
    904        // cm.getReds( reds );
    905        // cm.getGreens( greens );
    906        // cm.getBlues( blues );
    907        // return new BufferedImage( new IndexColorModel( 8, size, reds, greens, blues, pixel ), raster,
    908        // img.isAlphaPremultiplied(), null );
    909        // }
    910
    911        /**
    912         * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
    913         * capabilities/configuration
    914         *
    915         * @param g
    916         *            graphic context of the map
    917         * @param heigth
    918         *            height of the map in pixel
    919         */
    920        private void printCopyright( Graphics g, int heigth ) {
    921            WMSDeegreeParams dp = configuration.getDeegreeParams();
    922            String copyright = dp.getCopyRight();
    923            if ( copyrightImg != null ) {
    924                g.drawImage( copyrightImg, 8, heigth - copyrightImg.getHeight() - 5, null );
    925            } else {
    926                if ( copyright != null ) {
    927                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
    928                    g.setColor( Color.BLACK );
    929                    g.drawString( copyright, 8, heigth - 15 );
    930                    g.drawString( copyright, 10, heigth - 15 );
    931                    g.drawString( copyright, 8, heigth - 13 );
    932                    g.drawString( copyright, 10, heigth - 13 );
    933                    g.setColor( Color.WHITE );
    934                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
    935                    g.drawString( copyright, 9, heigth - 14 );
    936                }
    937            }
    938
    939        }
    940
    941        /**
    942         * @return the request that is being handled
    943         */
    944        protected GetMap getRequest() {
    945            return request;
    946        }
    947
    948        /**
    949         * @return the requests coordinate system
    950         */
    951        protected CoordinateSystem getRequestCRS() {
    952            return reqCRS;
    953        }
    954
    955    }