001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }