001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wms/DefaultGetMapHandler.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.wms;
044    
045    import java.awt.Color;
046    import java.awt.Font;
047    import java.awt.Graphics;
048    import java.awt.Graphics2D;
049    import java.awt.RenderingHints;
050    import java.awt.image.BufferedImage;
051    import java.util.ArrayList;
052    import java.util.LinkedList;
053    import java.util.List;
054    import java.util.concurrent.Callable;
055    import java.util.concurrent.CancellationException;
056    
057    import org.apache.batik.svggen.SVGGraphics2D;
058    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
059    import org.deegree.framework.concurrent.Executor;
060    import org.deegree.framework.log.ILogger;
061    import org.deegree.framework.log.LoggerFactory;
062    import org.deegree.framework.util.ImageUtils;
063    import org.deegree.framework.util.MapUtils;
064    import org.deegree.framework.util.MimeTypeMapper;
065    import org.deegree.graphics.MapFactory;
066    import org.deegree.graphics.Theme;
067    import org.deegree.graphics.optimizers.LabelOptimizer;
068    import org.deegree.graphics.sld.AbstractLayer;
069    import org.deegree.graphics.sld.AbstractStyle;
070    import org.deegree.graphics.sld.NamedLayer;
071    import org.deegree.graphics.sld.NamedStyle;
072    import org.deegree.graphics.sld.StyledLayerDescriptor;
073    import org.deegree.graphics.sld.UserLayer;
074    import org.deegree.graphics.sld.UserStyle;
075    import org.deegree.i18n.Messages;
076    import org.deegree.model.crs.CRSFactory;
077    import org.deegree.model.crs.CoordinateSystem;
078    import org.deegree.model.crs.GeoTransformer;
079    import org.deegree.model.spatialschema.Envelope;
080    import org.deegree.model.spatialschema.Geometry;
081    import org.deegree.model.spatialschema.GeometryFactory;
082    import org.deegree.ogcbase.InvalidSRSException;
083    import org.deegree.ogcwebservices.InconsistentRequestException;
084    import org.deegree.ogcwebservices.OGCWebServiceException;
085    import org.deegree.ogcwebservices.OGCWebServiceResponse;
086    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
087    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
088    import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
089    import org.deegree.ogcwebservices.wms.configuration.WMSConfiguration_1_3_0;
090    import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams;
091    import org.deegree.ogcwebservices.wms.operation.GetMap;
092    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
093    import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
094    import org.deegree.ogcwebservices.wms.operation.GetMap.Layer;
095    import org.w3c.dom.Element;
096    
097    /**
098     * 
099     * 
100     * @version $Revision: 9697 $
101     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
102     */
103    public class DefaultGetMapHandler implements GetMapHandler {
104    
105        private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
106    
107        private GetMap request = null;
108    
109        // private Object[] themes = null;
110    
111        private double scale = 0;
112    
113        private CoordinateSystem reqCRS = null;
114    
115        private WMSConfigurationType configuration = null;
116    
117        private BufferedImage copyrightImg = null;
118    
119        boolean version130 = false;
120    
121        /**
122         * Creates a new GetMapHandler object.
123         * 
124         * @param configuration
125         * @param request
126         *            request to perform
127         */
128        public DefaultGetMapHandler( WMSConfigurationType configuration, GetMap request ) {
129            this.request = request;
130            this.configuration = configuration;
131    
132            try {
133                // get copyright image if possible
134                copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyRight() );
135            } catch ( Exception e ) {
136                // don't use copyright
137            }
138    
139        }
140    
141        /**
142         * returns the configuration used by the handler
143         * 
144         * @return the configuration document
145         */
146        public WMSConfigurationType getConfiguration() {
147            return configuration;
148        }
149    
150        /**
151         * performs a GetMap request and retruns the result encapsulated within a <tt>GetMapResult</tt>
152         * object.
153         * <p>
154         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs
155         * that makes it imposible to return a result. If something wents wrong performing the request
156         * (none fatal error) The exception shall be encapsulated within the response object to be
157         * returned to the client as requested (GetMap-Request EXCEPTION-Parameter).
158         * 
159         * @return response to the GetMap response
160         */
161        public OGCWebServiceResponse performGetMap()
162                                throws OGCWebServiceException {
163    
164            // some initialization is done here because the constructor is called by reflection
165            // and the exceptions won't be properly handled in that case
166            try {
167                reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
168            } catch ( Exception e ) {
169                throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
170            }
171    
172            version130 = "1.3.0".equals( request.getVersion() );
173    
174            // exceeds the max allowed map width ?
175            int maxWidth = configuration.getDeegreeParams().getMaxMapWidth();
176            if ( ( maxWidth != 0 ) && ( request.getWidth() > maxWidth ) ) {
177                throw new InconsistentRequestException( Messages.getMessage( "WMS_EXCEEDS_WIDTH", new Integer( maxWidth ) ) );
178            }
179    
180            // exceeds the max allowed map height ?
181            int maxHeight = configuration.getDeegreeParams().getMaxMapHeight();
182            if ( ( maxHeight != 0 ) && ( request.getHeight() > maxHeight ) ) {
183                throw new InconsistentRequestException(
184                                                        Messages.getMessage( "WMS_EXCEEDS_HEIGHT", new Integer( maxHeight ) ) );
185            }
186    
187            try {
188                double pixelSize = 1;
189                if ( version130 ) {
190                    // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
191                    // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
192                    pixelSize = MapUtils.DEFAULT_PIXEL_SIZE;
193                }
194    
195                scale = MapUtils.calcScale( request.getWidth(), request.getHeight(), request.getBoundingBox(), reqCRS,
196                                            pixelSize );
197    
198                LOG.logInfo( "OGC WMS scale: " + scale );
199            } catch ( Exception e ) {
200                LOG.logError( e.getMessage(), e );
201                throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC" ) );
202            }
203    
204            GetMap.Layer[] ls = request.getLayers();
205    
206            // if 1.3.0, check for maximum allowed layers
207            if ( version130 ) {
208                WMSConfiguration_1_3_0 cfg = (WMSConfiguration_1_3_0) configuration;
209                if ( ls.length > cfg.getLayerLimit() ) {
210                    String ms = Messages.getMessage( "WMS_EXCEEDS_NUMBER", new Integer( cfg.getLayerLimit() ) );
211                    throw new InconsistentRequestException( ms );
212                }
213            }
214    
215            Layer[] oldLayers = ls;
216            ls = validateLayers( ls );
217    
218            LOG.logDebug( "Validated " + ls.length + " layers." );
219    
220            StyledLayerDescriptor sld = toSLD( oldLayers, request.getStyledLayerDescriptor() );
221    
222            AbstractLayer[] layers = sld.getLayers();
223    
224            LOG.logDebug( "After SLD consideration, found " + layers.length + " layers." );
225    
226            List<Callable<Object>> themes = new LinkedList<Callable<Object>>();
227    
228            for ( int i = 0; i < layers.length; i++ ) {
229    
230                if ( layers[i] instanceof NamedLayer ) {
231                    String styleName = null;
232                    if ( i < request.getLayers().length ) {
233                        styleName = request.getLayers()[i].getStyleName();
234                    }
235                    invokeNamedLayer( layers[i], styleName, themes );
236                } else {
237                    double sc = scale;
238                    if ( !version130 ) {
239                        // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
240                        // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
241                        sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
242                    }
243                    themes.add( new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], sc ) );
244                }
245            }
246    
247            Executor executor = Executor.getInstance();
248            try {
249                List<ExecutionFinishedEvent<Object>> results;
250                results = executor.performSynchronously( themes, configuration.getDeegreeParams().getRequestTimeLimit() );
251    
252                GetMapResult res = renderMap( results );
253                return res;
254            } catch ( InterruptedException e ) {
255                LOG.logError( e.getMessage(), e );
256                String s = Messages.getMessage( "WMS_WAITING" );
257                throw new OGCWebServiceException( getClass().getName(), s );
258            }
259        }
260    
261        /**
262         * this methods validates layer in two ways:<br>
263         * a) are layers available from the current WMS<br>
264         * b) If a layer is selected that includes other layers determine all its sublayers having
265         * <Name>s and return them instead
266         * 
267         * @param ls
268         * @return the layers
269         * @throws LayerNotDefinedException
270         * @throws InvalidSRSException
271         */
272        private Layer[] validateLayers( Layer[] ls )
273                                throws LayerNotDefinedException, InvalidSRSException {
274    
275            List<Layer> layer = new ArrayList<Layer>( ls.length );
276            for ( int i = 0; i < ls.length; i++ ) {
277                org.deegree.ogcwebservices.wms.capabilities.Layer l = configuration.getLayer( ls[i].getName() );
278    
279                if ( l == null ) {
280                    throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", ls[i].getName() ) );
281                }
282    
283                validateSRS( l.getSrs(), ls[i].getName() );
284    
285                layer.add( ls[i] );
286                if ( l.getLayer() != null ) {
287                    layer = addNestedLayers( l.getLayer(), ls[i].getStyleName(), layer );
288                }
289            }
290    
291            return layer.toArray( new Layer[layer.size()] );
292        }
293    
294        /**
295         * adds all direct and none direct sub-layers of the passed WMS capabilities layer as
296         * 
297         * @see GetMap.Layer to the passed list.
298         * @param layer
299         * @param reqLayer
300         * @param list
301         * @return all sublayers
302         * @throws InvalidSRSException
303         */
304        private List<Layer> addNestedLayers( org.deegree.ogcwebservices.wms.capabilities.Layer[] ll, String styleName,
305                                             List<Layer> list )
306                                throws InvalidSRSException {
307    
308            for ( int j = 0; j < ll.length; j++ ) {
309                if ( ll[j].getName() != null ) {
310                    String name = ll[j].getName();
311                    validateSRS( ll[j].getSrs(), name );
312                    list.add( GetMap.createLayer( name, styleName ) );
313                }
314                if ( ll[j].getLayer() != null ) {
315                    list = addNestedLayers( ll[j].getLayer(), styleName, list );
316                }
317    
318            }
319            return list;
320        }
321    
322        /**
323         * throws an exception if the requested SRS is not be supported by the passed layer (name)
324         * 
325         * @param srs
326         * @param name
327         * @throws InvalidSRSException
328         */
329        private void validateSRS( String[] srs, String name )
330                                throws InvalidSRSException {
331            boolean validSRS = false;
332            for ( int k = 0; k < srs.length; k++ ) {
333                validSRS = srs[k].equalsIgnoreCase( reqCRS.getIdentifier() );
334                if ( validSRS )
335                    break;
336            }
337            if ( !validSRS ) {
338                String s = Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", reqCRS.getIdentifier(), name );
339                throw new InvalidSRSException( s );
340            }
341        }
342    
343        private void invokeNamedLayer( AbstractLayer layer, String styleName, List<Callable<Object>> tasks )
344                                throws OGCWebServiceException {
345    
346            org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layer.getName() );
347    
348            LOG.logDebug( "Invoked layer " + layer.getName() );
349    
350            if ( validate( lay, layer.getName() ) ) {
351    
352                UserStyle us = getStyles( (NamedLayer) layer, styleName );
353                AbstractDataSource[] ds = lay.getDataSource();
354    
355                if ( ds.length == 0 ) {
356                    LOG.logDebug( "No datasources for layer " + layer.getName() );
357                } else {
358                    for ( int j = 0; j < ds.length; j++ ) {
359    
360                        LOG.logDebug( "Invoked datasource " + ds[j].getClass() + " for layer " + layer.getName() );
361    
362                        ScaleHint scaleHint = ds[j].getScaleHint();
363                        if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax()
364                             && isValidArea( ds[j].getValidArea() ) ) {
365                            double sc = scale;
366                            if ( !version130 ) {
367                                // required because for WMS 1.3.0 'scale' represents the
368                                // ScaleDenominator
369                                // and for WMS < 1.3.0 it represents the size of a pixel diagonal in
370                                // meter
371                                sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
372                            }
373    
374                            GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, (NamedLayer) layer, ds[j],
375                                                                                          us, sc );
376                            tasks.add( si );
377                        } else {
378                            LOG.logDebug( "Not showing layer " + layer.getName() + " due to scale" );
379                        }
380                    }
381                }
382            }
383        }
384    
385        /**
386         * returns true if the requested boundingbox intersects with the valid area of a datasource
387         * 
388         * @param validArea
389         */
390        private boolean isValidArea( Geometry validArea ) {
391    
392            if ( validArea != null ) {
393                try {
394                    Envelope env = request.getBoundingBox();
395                    Geometry geom = GeometryFactory.createSurface( env, reqCRS );
396                    if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
397                        // if requested CRS is not identical to the CRS of the valid area
398                        // a transformation must be performed before intersection can
399                        // be checked
400                        GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
401                        geom = gt.transform( geom );
402                    }
403                    return geom.intersects( validArea );
404                } catch ( Exception e ) {
405                    // should never happen
406                    LOG.logError( "Could not validate WMS datasource area", e );
407                }
408            }
409            return true;
410        }
411    
412        /**
413         * creates a StyledLayerDocument containing all requested layer, nested layers if required and
414         * assigend styles. Not considered are nested layers for mixed requests (LAYERS- and SLD(_BODY)-
415         * parameter has been defined)
416         * 
417         * @param layers
418         * @param inSLD
419         * @return a combined SLD object
420         * @throws InvalidSRSException
421         */
422        private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
423                                throws InvalidSRSException {
424            StyledLayerDescriptor sld = null;
425    
426            if ( layers != null && layers.length > 0 && inSLD == null ) {
427                // if just a list of layers has been requested
428    
429                // create a SLD from the requested LAYERS and assigned STYLES
430                List<AbstractLayer> al = new ArrayList<AbstractLayer>( layers.length * 2 );
431                for ( int i = 0; i < layers.length; i++ ) {
432                    AbstractStyle[] as = new AbstractStyle[] { new NamedStyle( layers[i].getStyleName() ) };
433                    al.add( new NamedLayer( layers[i].getName(), null, as ) );
434    
435                    // collect all named nested layers
436                    org.deegree.ogcwebservices.wms.capabilities.Layer lla;
437                    lla = configuration.getLayer( layers[i].getName() );
438                    List<GetMap.Layer> list = new ArrayList<GetMap.Layer>();
439                    addNestedLayers( lla.getLayer(), layers[i].getStyleName(), list );
440    
441                    // add nested layers to list of layers to be handled
442                    for ( int j = 0; j < list.size(); j++ ) {
443                        GetMap.Layer nestedLayer = list.get( j );
444                        as = new AbstractStyle[] { new NamedStyle( nestedLayer.getStyleName() ) };
445                        al.add( new NamedLayer( nestedLayer.getName(), null, as ) );
446                    }
447                }
448                sld = new StyledLayerDescriptor( al.toArray( new AbstractLayer[al.size()] ), "1.0.0" );
449            } else if ( layers != null && layers.length > 0 && inSLD != null ) {
450                // if layers not null and sld is not null then SLD layers just be
451                // considered if present in the layers list
452                // TODO
453                // layer with nested layers are not handled correctly and I think
454                // it really causes a lot of problems to use them in such a way
455                // because the style assigned to the mesting layer must be
456                // applicable for all nested layers.
457                List<String> list = new ArrayList<String>();
458                for ( int i = 0; i < layers.length; i++ ) {
459                    list.add( layers[i].getName() );
460                }
461    
462                List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
463                AbstractLayer[] al = inSLD.getLayers();
464                for ( int i = 0; i < al.length; i++ ) {
465                    if ( list.contains( al[i].getName() ) ) {
466                        newList.add( al[i] );
467                    }
468                }
469                al = new AbstractLayer[newList.size()];
470                sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
471    
472                // add nested layers for mixed case, nested from original sld
473                AbstractLayer[] as = inSLD.getLayers();
474                for ( AbstractLayer l : as ) {
475                    addNestedLayers( l, sld );
476                }
477            } else {
478                // if no layers but a SLD is defined ...
479                AbstractLayer[] as = inSLD.getLayers();
480                for ( AbstractLayer l : as ) {
481                    addNestedLayers( l, inSLD );
482                }
483    
484                sld = inSLD;
485            }
486    
487            return sld;
488        }
489    
490        // adds the nested layers to the sld
491        private void addNestedLayers( AbstractLayer l, StyledLayerDescriptor sld ) {
492            if ( !( l instanceof NamedLayer ) ) {
493                return;
494            }
495            if ( configuration.getLayer( l.getName() ) == null ) {
496                return;
497            }
498    
499            org.deegree.ogcwebservices.wms.capabilities.Layer[] ls;
500            ls = configuration.getLayer( l.getName() ).getLayer();
501            for ( org.deegree.ogcwebservices.wms.capabilities.Layer lay : ls ) {
502                NamedStyle sty = new NamedStyle( lay.getStyles()[0].getName() );
503                AbstractStyle[] newSty = new AbstractStyle[] { sty };
504                NamedLayer newLay = new NamedLayer( lay.getName(), null, newSty );
505                sld.addLayer( newLay );
506            }
507        }
508    
509        /**
510         * returns the <tt>UserStyle</tt>s assigned to a named layer
511         * 
512         * @param sldLayer
513         *            layer to get the styles for
514         * @param styleName
515         *            requested stylename (from the KVP encoding)
516         */
517        private UserStyle getStyles( NamedLayer sldLayer, String styleName )
518                                throws OGCWebServiceException {
519    
520            AbstractStyle[] styles = sldLayer.getStyles();
521            UserStyle us = null;
522    
523            // to avoid retrieving the layer again for each style
524            org.deegree.ogcwebservices.wms.capabilities.Layer layer = null;
525            layer = configuration.getLayer( sldLayer.getName() );
526            int i = 0;
527            while ( us == null && i < styles.length ) {
528                if ( styles[i] instanceof NamedStyle ) {
529                    // styles will be taken from the WMS's style repository
530                    us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
531                } else {
532                    // if the requested style fits the name of the defined style or
533                    // if the defined style is marked as default and the requested
534                    // style if 'default' the condition is true. This includes that
535                    // if more than one style with the same name or more than one
536                    // style is marked as default always the first will be choosen
537                    if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
538                         || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
539                        us = (UserStyle) styles[i];
540                    }
541                }
542                i++;
543            }
544            if ( us == null ) {
545                // this may happens if the SLD contains a named layer but not
546                // a style! yes this is valid according to SLD spec 1.0.0
547                us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
548            }
549            return us;
550        }
551    
552        /**
553         * 
554         * @param styleName
555         * @param layerName
556         * @param layer
557         * @return the style
558         * @throws StyleNotDefinedException
559         */
560        private UserStyle getPredefinedStyle( String styleName, String layerName,
561                                              org.deegree.ogcwebservices.wms.capabilities.Layer layer )
562                                throws StyleNotDefinedException {
563            UserStyle us = null;
564            if ( "default".equals( styleName ) ) {
565                us = layer.getStyle( styleName );
566            }
567    
568            if ( us == null ) {
569                if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
570                     || styleName.equals( "default" ) ) {
571                    styleName = "default:" + layerName;
572                }
573            }
574    
575            us = layer.getStyle( styleName );
576    
577            if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
578                String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
579                throw new StyleNotDefinedException( s );
580            }
581            return us;
582        }
583    
584        /**
585         * validates if the requested layer matches the conditions of the request if not a
586         * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't
587         * able to deviever data for the requested area and/or scale false will be returned. If the
588         * layer matches the request and contains data for the requested area and/or scale true will be
589         * returned.
590         * 
591         * @param layer
592         *            layer as defined at the capabilities/configuration
593         * @param name
594         *            name of the layer (must be submitted seperatly because the layer parameter can be
595         *            <tt>null</tt>
596         */
597        private boolean validate( org.deegree.ogcwebservices.wms.capabilities.Layer layer, String name )
598                                throws OGCWebServiceException {
599    
600            // check if layer is available
601            if ( layer == null ) {
602                throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", name ) );
603            }
604    
605            // check bounding box
606            try {
607                Envelope bbox = request.getBoundingBox();
608                Envelope layerBbox = layer.getLatLonBoundingBox();
609                if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
610                    // transform the bounding box of the request to EPSG:4326
611                    GeoTransformer gt = new GeoTransformer( CRSFactory.create( "epsg:4326" ) );
612                    bbox = gt.transform( bbox, reqCRS );
613                }
614                if ( !bbox.intersects( layerBbox ) ) {
615                    LOG.logDebug( "Not showing layer because the request is out of the bounding box." );
616                    return false;
617                }
618    
619            } catch ( Exception e ) {
620                LOG.logError( e.getMessage(), e );
621                throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
622            }
623    
624            return true;
625        }
626    
627        /**
628         * renders the map from the <tt>DisplayElement</tt>s
629         */
630        private GetMapResult renderMap( List<ExecutionFinishedEvent<Object>> results ) {
631    
632            GetMapResult response = null;
633            OGCWebServiceException exce = null;
634    
635            ArrayList<Object> list = new ArrayList<Object>( 50 );
636            for ( ExecutionFinishedEvent<Object> evt : results ) {
637                Object o = null;
638    
639                // exception handling might be handled in a better way
640                try {
641                    o = evt.getResult();
642                } catch ( CancellationException e ) {
643                    exce = new OGCWebServiceException( getClass().getName(), e.toString() );
644                } catch ( Throwable e ) {
645                    exce = new OGCWebServiceException( getClass().getName(), e.toString() );
646                }
647    
648                if ( o instanceof Exception ) {
649                    exce = new OGCWebServiceException( getClass().getName(), o.toString() );
650                }
651                if ( o instanceof OGCWebServiceException ) {
652                    exce = (OGCWebServiceException) o;
653                    break;
654                }
655                if ( o != null ) {
656                    list.add( o );
657                }
658            }
659    
660            String mime = MimeTypeMapper.toMimeType( request.getFormat() );
661    
662            // get target object for rendering
663            Object target = GraphicContextFactory.createGraphicTarget( mime, request.getWidth(), request.getHeight() );
664    
665            // get graphic context of the target
666            Graphics g = GraphicContextFactory.createGraphicContext( mime, target );
667            if ( exce == null ) {
668                // only if no exception occured
669                try {
670                    Theme[] th = list.toArray( new Theme[list.size()] );
671                    org.deegree.graphics.MapView map = null;
672                    if ( th.length > 0 ) {
673                        map = MapFactory.createMapView( "deegree WMS", request.getBoundingBox(), reqCRS, th,
674                                                        MapUtils.DEFAULT_PIXEL_SIZE );
675                    }
676                    g.setClip( 0, 0, request.getWidth(), request.getHeight() );
677    
678                    if ( !request.getTransparency() ) {
679                        if ( g instanceof Graphics2D ) {
680                            // this ensures real clearing (rendering modifies the color ever so
681                            // slightly)
682                            ( (Graphics2D) g ).setBackground( request.getBGColor() );
683                            g.clearRect( 0, 0, request.getWidth(), request.getHeight() );
684                        } else {
685                            g.setColor( request.getBGColor() );
686                            g.fillRect( 0, 0, request.getWidth(), request.getHeight() );
687                        }
688                    }
689    
690                    if ( map != null ) {
691                        Theme[] thms = map.getAllThemes();
692                        map.addOptimizer( new LabelOptimizer( thms ) );
693                        // antialiasing must be switched of for gif output format
694                        // because the antialiasing may create more than 255 colors
695                        // in the map/image, even just a few colors are defined in
696                        // the styles
697                        if ( !request.getFormat().equalsIgnoreCase( "image/gif" ) ) {
698                            if ( configuration.getDeegreeParams().isAntiAliased() ) {
699                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
700                                                                     RenderingHints.VALUE_ANTIALIAS_ON );
701                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
702                                                                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
703                            }
704                        }
705                        map.paint( g );
706                    }
707                } catch ( Exception e ) {
708                    LOG.logError( e.getMessage(), e );
709                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
710                }
711            }
712    
713            // print a copyright note at the left lower corner of the map
714            printCopyright( g, request.getHeight() );
715    
716            if ( mime.equals( "image/svg+xml" ) || mime.equals( "image/svg xml" ) ) {
717                Element root = ( (SVGGraphics2D) g ).getRoot();
718                root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
719                response = WMSProtocolFactory.createGetMapResponse( request, exce, root );
720            } else {
721                response = WMSProtocolFactory.createGetMapResponse( request, exce, target );
722            }
723            g.dispose();
724    
725            return response;
726        }
727    
728        /**
729         * prints a copyright note at left side of the map bottom. The copyright note will be extracted
730         * from the WMS capabilities/configuration
731         * 
732         * @param g
733         *            graphic context of the map
734         * @param heigth
735         *            height of the map in pixel
736         */
737        private void printCopyright( Graphics g, int heigth ) {
738            WMSDeegreeParams dp = configuration.getDeegreeParams();
739            String copyright = dp.getCopyRight();
740            if ( copyrightImg != null ) {
741                g.drawImage( copyrightImg, 8, heigth - copyrightImg.getHeight() - 5, null );
742            } else {
743                if ( copyright != null ) {
744                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
745                    g.setColor( Color.BLACK );
746                    g.drawString( copyright, 8, heigth - 15 );
747                    g.drawString( copyright, 10, heigth - 15 );
748                    g.drawString( copyright, 8, heigth - 13 );
749                    g.drawString( copyright, 10, heigth - 13 );
750                    g.setColor( Color.WHITE );
751                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
752                    g.drawString( copyright, 9, heigth - 14 );
753                }
754            }
755    
756        }
757    
758        /**
759         * @return the request that is being handled
760         */
761        protected GetMap getRequest() {
762            return request;
763        }
764    
765        /**
766         * @return the requests coordinate system
767         */
768        protected CoordinateSystem getRequestCRS() {
769            return reqCRS;
770        }
771    
772    }