001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wms/DefaultGetMapHandler.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 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.List;
053    import java.util.concurrent.Callable;
054    
055    import org.apache.batik.svggen.SVGGraphics2D;
056    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
057    import org.deegree.framework.concurrent.ExecutionFinishedListener;
058    import org.deegree.framework.concurrent.Executor;
059    import org.deegree.framework.log.ILogger;
060    import org.deegree.framework.log.LoggerFactory;
061    import org.deegree.framework.util.ImageUtils;
062    import org.deegree.framework.util.MapUtils;
063    import org.deegree.framework.util.MimeTypeMapper;
064    import org.deegree.graphics.MapFactory;
065    import org.deegree.graphics.Theme;
066    import org.deegree.graphics.optimizers.LabelOptimizer;
067    import org.deegree.graphics.sld.AbstractLayer;
068    import org.deegree.graphics.sld.AbstractStyle;
069    import org.deegree.graphics.sld.NamedLayer;
070    import org.deegree.graphics.sld.NamedStyle;
071    import org.deegree.graphics.sld.StyledLayerDescriptor;
072    import org.deegree.graphics.sld.UserLayer;
073    import org.deegree.graphics.sld.UserStyle;
074    import org.deegree.i18n.Messages;
075    import org.deegree.model.crs.CRSFactory;
076    import org.deegree.model.crs.CoordinateSystem;
077    import org.deegree.model.crs.GeoTransformer;
078    import org.deegree.model.crs.IGeoTransformer;
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: 6846 $
101     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
102     */
103    public class DefaultGetMapHandler implements GetMapHandler, ExecutionFinishedListener<Object[]> {
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 int count = 0;
114    
115        private CoordinateSystem reqCRS = null;
116    
117        private WMSConfigurationType configuration = null;
118    
119        private BufferedImage copyrightImg = null;
120    
121        boolean version130 = false;
122    
123        /**
124         * Creates a new GetMapHandler object.
125         * 
126         * @param configuration
127         * @param request
128         *            request to perform
129         */
130        public DefaultGetMapHandler( WMSConfigurationType configuration, GetMap request ) {
131            this.request = request;
132            this.configuration = configuration;
133    
134            try {
135                // get copyright image if possible
136                copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyRight() );
137            } catch ( Exception e ) {
138                // don't use copyright
139            }
140    
141        }
142    
143        /**
144         * returns the configuration used by the handler
145         * 
146         * @return the configuration document
147         */
148        public WMSConfigurationType getConfiguration() {
149            return configuration;
150        }
151    
152        /**
153         * increases the counter variable that holds the number of services that has sent a response.
154         * All data are available if the counter value equals the number of requested layers.
155         */
156        protected synchronized void increaseCounter() {
157            count++;
158        }
159    
160        /**
161         * performs a GetMap request and retruns the result encapsulated within a <tt>GetMapResult</tt>
162         * object.
163         * <p>
164         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs
165         * that makes it imposible to return a result. If something wents wrong performing the request
166         * (none fatal error) The exception shall be encapsulated within the response object to be
167         * returned to the client as requested (GetMap-Request EXCEPTION-Parameter).
168         * 
169         * @return response to the GetMap response
170         */
171        public OGCWebServiceResponse performGetMap()
172                                throws OGCWebServiceException {
173    
174            // some initialization is done here because the constructor is called by reflection
175            // and the exceptions won't be properly handled in that case
176            try {
177                reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
178            } catch ( Exception e ) {
179                throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
180            }
181    
182            version130 = "1.3.0".equals( request.getVersion() );
183    
184            // exceeds the max allowed map width ?
185            int maxWidth = configuration.getDeegreeParams().getMaxMapWidth();
186            if ( ( maxWidth != 0 ) && ( request.getWidth() > maxWidth ) ) {
187                throw new InconsistentRequestException( Messages.getMessage( "WMS_EXCEEDS_WIDTH", new Integer( maxWidth ) ) );
188            }
189    
190            // exceeds the max allowed map height ?
191            int maxHeight = configuration.getDeegreeParams().getMaxMapHeight();
192            if ( ( maxHeight != 0 ) && ( request.getHeight() > maxHeight ) ) {
193                throw new InconsistentRequestException(
194                                                        Messages.getMessage( "WMS_EXCEEDS_HEIGHT", new Integer( maxHeight ) ) );
195            }
196    
197            try {
198                double pixelSize = 1;
199                if ( version130 ) {
200                    // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
201                    // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
202                    pixelSize = MapUtils.DEFAULT_PIXEL_SIZE;
203                }
204    
205                scale = MapUtils.calcScale( request.getWidth(), request.getHeight(), request.getBoundingBox(), reqCRS,
206                                            pixelSize );
207    
208                LOG.logInfo( "OGC WMS scale: " + scale );
209            } catch ( Exception e ) {
210                LOG.logError( e.getMessage(), e );
211                throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC" ) );
212            }
213    
214            GetMap.Layer[] ls = request.getLayers();
215    
216            // if 1.3.0, check for maximum allowed layers
217            if ( version130 ) {
218                WMSConfiguration_1_3_0 cfg = (WMSConfiguration_1_3_0) configuration;
219                if ( ls.length > cfg.getLayerLimit() ) {
220                    String ms = Messages.getMessage( "WMS_EXCEEDS_NUMBER", new Integer( cfg.getLayerLimit() ) );
221                    throw new InconsistentRequestException( ms );
222                }
223            }
224    
225            ls = validateLayers( ls );
226    
227            LOG.logDebug( "Validated " + ls.length + " layers." );
228    
229            StyledLayerDescriptor sld = toSLD( ls, request.getStyledLayerDescriptor() );
230    
231            AbstractLayer[] layers = sld.getLayers();
232    
233            LOG.logDebug( "After SLD consideration, found " + layers.length + " layers." );
234    
235            // get the number of themes assigned to the selected layers
236            // notice that there maybe more themes as there are layers because
237            // 1 .. n datasources can be assigned to one layer.
238            int cntTh = countNumberOfThemes( layers );
239    
240            themes = new Object[cntTh];
241            // invokes the data supplyer for each layer in an independ thread
242            int kk = 0;
243    
244            for ( int i = 0; i < layers.length; i++ ) {
245    
246                if ( layers[i] instanceof NamedLayer ) {
247                    String styleName = null;
248                    if ( i < request.getLayers().length ) {
249                        styleName = request.getLayers()[i].getStyleName();
250                    }
251                    // please note that this may be undesirable behaviour, I (schmitz) just added
252                    // the safety 'if' because it will throw nasty exceptions otherwise
253                    // (I don't know what this code actually does)
254                    kk = invokeNamedLayer( layers[i], kk, styleName );
255                    // if ( kk < cntTh ) {
256                    // kk = invokeNamedLayer( layers[i], kk, styleName );
257                    // }
258                } else {
259                    double sc = scale;
260                    if ( !version130 ) {
261                        // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
262                        // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
263                        sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
264                    }
265                    GetMapServiceInvokerForUL si = new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], sc, kk++ );
266                    new Thread( si ).start();
267                }
268            }
269    
270            LOG.logDebug( "Invoked " + kk + " layers." );
271    
272            // TODO
273            // substitue by an event based approach
274            waitForFinished();
275    
276            GetMapResult res = renderMap();
277    
278            return res;
279        }
280    
281        /**
282         * this methods validates layer in two ways:<br>
283         * a) are layers available from the current WMS<br>
284         * b) If a layer is selected that includes other layers determine all its sublayers having
285         * <Name>s and return them instead
286         * 
287         * @param ls
288         * @return the layers
289         * @throws LayerNotDefinedException
290         * @throws InvalidSRSException
291         */
292        private Layer[] validateLayers( Layer[] ls )
293                                throws LayerNotDefinedException, InvalidSRSException {
294    
295            List<Layer> layer = new ArrayList<Layer>( ls.length );
296            for ( int i = 0; i < ls.length; i++ ) {
297                org.deegree.ogcwebservices.wms.capabilities.Layer l = configuration.getLayer( ls[i].getName() );
298    
299                if ( l == null ) {
300                    throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", ls[i].getName() ) );
301                }
302    
303                validateSRS( l.getSrs(), ls[i].getName() );
304    
305                layer.add( ls[i] );
306                if ( l.getLayer() != null ) {
307                    layer = addNestedLayers( l.getLayer(), ls[i].getStyleName(), layer );
308                }
309            }
310    
311            return layer.toArray( new Layer[layer.size()] );
312        }
313    
314        /**
315         * adds all direct and none direct sub-layers of the passed WMS capabilities layer as
316         * 
317         * @see GetMap.Layer to the passed list.
318         * @param layer
319         * @param reqLayer
320         * @param list
321         * @return all sublayers
322         * @throws InvalidSRSException
323         */
324        private List<Layer> addNestedLayers( org.deegree.ogcwebservices.wms.capabilities.Layer[] ll, String styleName,
325                                             List<Layer> list )
326                                throws InvalidSRSException {
327    
328            for ( int j = 0; j < ll.length; j++ ) {
329                if ( ll[j].getName() != null ) {
330                    String name = ll[j].getName();
331                    validateSRS( ll[j].getSrs(), name );
332                    list.add( GetMap.createLayer( name, styleName ) );
333                }
334                if ( ll[j].getLayer() != null ) {
335                    list = addNestedLayers( ll[j].getLayer(), styleName, list );
336                }
337    
338            }
339            return list;
340        }
341    
342        /**
343         * throws an exception if the requested SRS is not be supported by the passed layer (name)
344         * 
345         * @param srs
346         * @param name
347         * @throws InvalidSRSException
348         */
349        private void validateSRS( String[] srs, String name )
350                                throws InvalidSRSException {
351            boolean validSRS = false;
352            for ( int k = 0; k < srs.length; k++ ) {
353                validSRS = srs[k].equalsIgnoreCase( reqCRS.getName() );
354                if ( validSRS )
355                    break;
356            }
357            if ( !validSRS ) {
358                String s = Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", reqCRS.getName(), name );
359                throw new InvalidSRSException( s );
360            }
361        }
362    
363        /**
364         * @param layers
365         * @param kk
366         * @param i
367         * @return a counter, kk + 1 I guess
368         * @throws OGCWebServiceException
369         */
370        private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName )
371                                throws OGCWebServiceException {
372    
373            org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layer.getName() );
374    
375            LOG.logDebug( "Invoked layer " + layer.getName() );
376    
377            if ( validate( lay, layer.getName() ) ) {
378    
379                UserStyle us = getStyles( (NamedLayer) layer, styleName );
380                AbstractDataSource[] ds = lay.getDataSource();
381    
382                if ( ds.length == 0 ) {
383                    increaseCounter();
384                    LOG.logDebug( "No datasources for layer " + layer.getName() );
385                } else {
386                    for ( int j = 0; j < ds.length; j++ ) {
387    
388                        LOG.logDebug( "Invoked datasource " + ds[j].getClass() + " for layer " + layer.getName() );
389    
390                        ScaleHint scaleHint = ds[j].getScaleHint();
391                        if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax()
392                             && isValidArea( ds[j].getValidArea() ) ) {
393                            double sc = scale;
394                            if ( !version130 ) {
395                                // required because for WMS 1.3.0 'scale' represents the
396                                // ScaleDenominator
397                                // and for WMS < 1.3.0 it represents the size of a pixel diagonal in
398                                // meter
399                                sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
400                            }
401    
402                            GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, (NamedLayer) layer, ds[j],
403                                                                                          us, sc, kk++ );
404                            MapServiceTask task = new MapServiceTask( si );
405                            try {
406                                Executor.getInstance().performAsynchronously( task, this );
407                            } catch ( Exception e ) {
408                                LOG.logError( e.getMessage(), e );
409                                throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
410                            }
411                        } else {
412                            increaseCounter();
413                            LOG.logDebug( "Not showing layer " + layer.getName() + " due to scale" );
414                        }
415                    }
416                }
417            } else {
418                // set theme to null if no data are available for the requested
419                // area and/or scale. This will cause this index position will be ignored
420                // when creating the final result
421                themes[kk++] = null;
422                AbstractDataSource[] ds = lay.getDataSource();
423                for ( int i = 0; i < ds.length; ++i ) {
424                    increaseCounter();
425                }
426            }
427            return kk;
428        }
429    
430        /**
431         * returns the number of <code>DataSource</code>s involved in a GetMap request
432         * 
433         * @param layers
434         * @return the number of themes
435         * @throws OGCWebServiceException
436         * @throws OGCWebServiceException
437         */
438        private int countNumberOfThemes( AbstractLayer[] layers )
439                                throws OGCWebServiceException {
440            int cnt = 0;
441            for ( int i = 0; i < layers.length; i++ ) {
442                if ( layers[i] instanceof NamedLayer ) {
443                    org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layers[i].getName() );
444                    validate( lay, layers[i].getName() );
445    
446                    if ( lay != null ) {
447                        AbstractDataSource[] ds = lay.getDataSource();
448                        if ( ds.length == 0 ) {
449                            ++cnt;
450                        } else {
451                            for ( int j = 0; j < ds.length; j++ ) {
452                                cnt++;
453                            }
454                        }
455                    }
456                } else {
457                    cnt++;
458                }
459            }
460    
461            LOG.logDebug( "Counted " + cnt + " themes." );
462            return cnt;
463        }
464    
465        /**
466         * returns true if the requested boundingbox intersects with the valid area of a datasource
467         * 
468         * @param validArea
469         */
470        private boolean isValidArea( Geometry validArea ) {
471    
472            if ( validArea != null ) {
473                try {
474                    Envelope env = request.getBoundingBox();
475                    Geometry geom = GeometryFactory.createSurface( env, reqCRS );
476                    if ( !reqCRS.getName().equals( validArea.getCoordinateSystem().getName() ) ) {
477                        // if requested CRS is not identical to the CRS of the valid area
478                        // a transformation must be performed before intersection can
479                        // be checked
480                        IGeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
481                        geom = gt.transform( geom );
482                    }
483                    return geom.intersects( validArea );
484                } catch ( Exception e ) {
485                    // should never happen
486                    LOG.logError( "Could not validate WMS datasource area", e );
487                }
488            }
489            return true;
490        }
491    
492        /**
493         * runs a loop until all sub requestes (one for each layer) has been finished or the maximum
494         * time limit has been exceeded.
495         * 
496         * @throws OGCWebServiceException
497         */
498        private void waitForFinished()
499                                throws OGCWebServiceException {
500            if ( count < themes.length ) {
501                // waits until the requested layers are available as <tt>DisplayElements</tt>
502                // or the time limit has been reached.
503                // if count == themes.length then no request must be performed
504                long timeStamp = System.currentTimeMillis();
505                long lapse = 0;
506                long timeout = configuration.getDeegreeParams().getRequestTimeLimit();
507                LOG.logDebug( "Timeout is set to " + timeout + " ms." );
508                do {
509                    try {
510                        Thread.sleep( 50 );
511                        lapse += 50;
512                        LOG.logDebug( "Counter is now " + count + "." );
513                    } catch ( InterruptedException e ) {
514                        LOG.logError( e.getMessage(), e );
515                        String s = Messages.getMessage( "WMS_WAITING" );
516                        throw new OGCWebServiceException( getClass().getName(), s );
517                    }
518                } while ( count < themes.length && lapse < timeout );
519                if ( System.currentTimeMillis() - timeStamp >= timeout ) {
520                    String s = Messages.getMessage( "WMS_TIMEOUT" );
521                    LOG.logError( s );
522                    throw new OGCWebServiceException( getClass().getName(), s );
523                }
524            }
525            LOG.logDebug( "Counter is now " + count );
526        }
527    
528        /**
529         * creates a StyledLayerDocument containing all requested layer, nested layers if required and
530         * assigend styles. Not considered are nested layers for mixed requests (LAYERS- and SLD(_BODY)-
531         * parameter has been defined)
532         * 
533         * @param layers
534         * @param inSLD
535         * @return a combined SLD object
536         * @throws InvalidSRSException
537         */
538        private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
539                                throws InvalidSRSException {
540            StyledLayerDescriptor sld = null;
541    
542            if ( layers != null && layers.length > 0 && inSLD == null ) {
543                // if just a list of layers has been requested
544    
545                // create a SLD from the requested LAYERS and assigned STYLES
546                List<AbstractLayer> al = new ArrayList<AbstractLayer>( layers.length * 2 );
547                for ( int i = 0; i < layers.length; i++ ) {
548                    AbstractStyle[] as = new AbstractStyle[] { new NamedStyle( layers[i].getStyleName() ) };
549                    al.add( new NamedLayer( layers[i].getName(), null, as ) );
550    
551                    // collect all named nested layers
552                    org.deegree.ogcwebservices.wms.capabilities.Layer lla;
553                    lla = configuration.getLayer( layers[i].getName() );
554                    List<GetMap.Layer> list = new ArrayList<GetMap.Layer>();
555                    addNestedLayers( lla.getLayer(), layers[i].getStyleName(), list );
556    
557                    // add nested layers to list of layers to be handled
558                    for ( int j = 0; j < list.size(); j++ ) {
559                        GetMap.Layer nestedLayer = list.get( j );
560                        as = new AbstractStyle[] { new NamedStyle( nestedLayer.getStyleName() ) };
561                        al.add( new NamedLayer( nestedLayer.getName(), null, as ) );
562                    }
563                }
564                sld = new StyledLayerDescriptor( al.toArray( new AbstractLayer[al.size()] ), "1.0.0" );
565            } else if ( layers != null && layers.length > 0 && inSLD != null ) {
566                // if layers not null and sld is not null then SLD layers just be
567                // considered if present in the layers list
568                // TODO
569                // layer with nested layers are not handled correctly and I think
570                // it really causes a lot of problems to use them in such a way
571                // because the style assigned to the mesting layer must be
572                // applicable for all nested layers.
573                List<String> list = new ArrayList<String>();
574                for ( int i = 0; i < layers.length; i++ ) {
575                    list.add( layers[i].getName() );
576                }
577    
578                List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
579                AbstractLayer[] al = inSLD.getLayers();
580                for ( int i = 0; i < al.length; i++ ) {
581                    if ( list.contains( al[i].getName() ) ) {
582                        newList.add( al[i] );
583                    }
584                }
585                al = new AbstractLayer[newList.size()];
586                sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
587    
588                // add nested layers for mixed case, nested from original sld
589                AbstractLayer[] as = inSLD.getLayers();
590                for ( AbstractLayer l : as ) {
591                    addNestedLayers( l, sld );
592                }
593            } else {
594                // if no layers but a SLD is defined ...
595                AbstractLayer[] as = inSLD.getLayers();
596                for ( AbstractLayer l : as ) {
597                    addNestedLayers( l, inSLD );
598                }
599    
600                sld = inSLD;
601            }
602    
603            return sld;
604        }
605    
606        // adds the nested layers to the sld
607        private void addNestedLayers( AbstractLayer l, StyledLayerDescriptor sld ) {
608            if ( !( l instanceof NamedLayer ) ) {
609                return;
610            }
611            if ( configuration.getLayer( l.getName() ) == null ) {
612                return;
613            }
614    
615            org.deegree.ogcwebservices.wms.capabilities.Layer[] ls;
616            ls = configuration.getLayer( l.getName() ).getLayer();
617            for ( org.deegree.ogcwebservices.wms.capabilities.Layer lay : ls ) {
618                NamedStyle sty = new NamedStyle( lay.getStyles()[0].getName() );
619                AbstractStyle[] newSty = new AbstractStyle[] { sty };
620                NamedLayer newLay = new NamedLayer( lay.getName(), null, newSty );
621                sld.addLayer( newLay );
622            }
623        }
624    
625        /**
626         * returns the <tt>UserStyle</tt>s assigned to a named layer
627         * 
628         * @param sldLayer
629         *            layer to get the styles for
630         * @param styleName
631         *            requested stylename (from the KVP encoding)
632         */
633        private UserStyle getStyles( NamedLayer sldLayer, String styleName )
634                                throws OGCWebServiceException {
635    
636            AbstractStyle[] styles = sldLayer.getStyles();
637            UserStyle us = null;
638    
639            // to avoid retrieving the layer again for each style
640            org.deegree.ogcwebservices.wms.capabilities.Layer layer = null;
641            layer = configuration.getLayer( sldLayer.getName() );
642            int i = 0;
643            while ( us == null && i < styles.length ) {
644                if ( styles[i] instanceof NamedStyle ) {
645                    // styles will be taken from the WMS's style repository
646                    us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
647                } else {
648                    // if the requested style fits the name of the defined style or
649                    // if the defined style is marked as default and the requested
650                    // style if 'default' the condition is true. This includes that
651                    // if more than one style with the same name or more than one
652                    // style is marked as default always the first will be choosen
653                    if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
654                         || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
655                        us = (UserStyle) styles[i];
656                    }
657                }
658                i++;
659            }
660            if ( us == null ) {
661                // this may happens if the SLD contains a named layer but not
662                // a style! yes this is valid according to SLD spec 1.0.0
663                us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
664            }
665            return us;
666        }
667    
668        /**
669         * 
670         * @param styleName
671         * @param layerName
672         * @param layer
673         * @return the style
674         * @throws StyleNotDefinedException
675         */
676        private UserStyle getPredefinedStyle( String styleName, String layerName,
677                                              org.deegree.ogcwebservices.wms.capabilities.Layer layer )
678                                throws StyleNotDefinedException {
679            UserStyle us = null;
680            if ( "default".equals( styleName ) ) {
681                us = layer.getStyle( styleName );
682            }
683    
684            if ( us == null ) {
685                if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
686                     || styleName.equals( "default" ) ) {
687                    styleName = "default:" + layerName;
688                }
689            }
690    
691            us = layer.getStyle( styleName );
692    
693            if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
694                String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
695                throw new StyleNotDefinedException( s );
696            }
697            return us;
698        }
699    
700        /**
701         * validates if the requested layer matches the conditions of the request if not a
702         * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't
703         * able to deviever data for the requested area and/or scale false will be returned. If the
704         * layer matches the request and contains data for the requested area and/or scale true will be
705         * returned.
706         * 
707         * @param layer
708         *            layer as defined at the capabilities/configuration
709         * @param name
710         *            name of the layer (must be submitted seperatly because the layer parameter can be
711         *            <tt>null</tt>
712         */
713        private boolean validate( org.deegree.ogcwebservices.wms.capabilities.Layer layer, String name )
714                                throws OGCWebServiceException {
715    
716            // check if layer is available
717            if ( layer == null ) {
718                throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", name ) );
719            }
720    
721            // check bounding box
722            try {
723                Envelope bbox = request.getBoundingBox();
724                Envelope layerBbox = layer.getLatLonBoundingBox();
725                if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
726                    // transform the bounding box of the request to EPSG:4326
727                    IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "epsg:4326" ) );
728                    bbox = gt.transform( bbox, reqCRS );
729                }
730                if ( !bbox.intersects( layerBbox ) ) {
731                    return false;
732                }
733    
734            } catch ( Exception e ) {
735                LOG.logError( e.getMessage(), e );
736                throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
737            }
738    
739            return true;
740        }
741    
742        /**
743         * put a theme to the passed index of the themes array. The second param passed is a
744         * <tt>Theme</tt> or an exception
745         */
746    
747        protected synchronized void putTheme( int index, Object o ) {
748            themes[index] = o;
749        }
750    
751        /**
752         * will be called each time a datasource has been read
753         * 
754         * @param finishedEvent
755         */
756        public synchronized void executionFinished( ExecutionFinishedEvent<Object[]> finishedEvent ) {
757            Object[] o = null;
758            try {
759                o = finishedEvent.getResult();
760            } catch ( Throwable t ) {
761                String msg = Messages.getMessage( "WMS_ASYNC_TASK_ERROR", t.getMessage() );
762                LOG.logError( msg, t );
763            }
764            themes[( (Integer) o[0] ).intValue()] = o[1];
765            increaseCounter();
766        }
767    
768        /**
769         * renders the map from the <tt>DisplayElement</tt>s
770         */
771        private synchronized GetMapResult renderMap() {
772    
773            GetMapResult response = null;
774            OGCWebServiceException exce = null;
775    
776            ArrayList<Object> list = new ArrayList<Object>( 50 );
777            for ( int i = 0; i < themes.length; i++ ) {
778                if ( themes[i] instanceof Exception ) {
779                    exce = new OGCWebServiceException( getClass().getName(), themes[i].toString() );
780                }
781                if ( themes[i] instanceof OGCWebServiceException ) {
782                    exce = (OGCWebServiceException) themes[i];
783                    break;
784                }
785                if ( themes[i] != null ) {
786                    list.add( themes[i] );
787                }
788            }
789    
790            String mime = MimeTypeMapper.toMimeType( request.getFormat() );
791    
792            // get target object for rendering
793            Object target = GraphicContextFactory.createGraphicTarget( mime, request.getWidth(), request.getHeight() );
794    
795            // get graphic context of the target
796            Graphics g = GraphicContextFactory.createGraphicContext( mime, target );
797            if ( exce == null ) {
798                // only if no exception occured
799                try {
800                    Theme[] th = list.toArray( new Theme[list.size()] );
801                    org.deegree.graphics.MapView map = null;
802                    if ( th.length > 0 ) {
803                        map = MapFactory.createMapView( "deegree WMS", request.getBoundingBox(), reqCRS, th );
804                    }
805                    g.setClip( 0, 0, request.getWidth(), request.getHeight() );
806    
807                    if ( !request.getTransparency() ) {
808                        if ( g instanceof Graphics2D ) {
809                            // this ensures real clearing (rendering modifies the color ever so
810                            // slightly)
811                            ( (Graphics2D) g ).setBackground( request.getBGColor() );
812                            g.clearRect( 0, 0, request.getWidth(), request.getHeight() );
813                        } else {
814                            g.setColor( request.getBGColor() );
815                            g.fillRect( 0, 0, request.getWidth(), request.getHeight() );
816                        }
817                    }
818    
819                    if ( map != null ) {
820                        Theme[] thms = map.getAllThemes();
821                        map.addOptimizer( new LabelOptimizer( thms ) );
822                        // antialiasing must be switched of for gif output format
823                        // because the antialiasing may create more than 255 colors
824                        // in the map/image, even just a few colors are defined in
825                        // the styles
826                        if ( !request.getFormat().equalsIgnoreCase( "image/gif" ) ) {
827                            if ( configuration.getDeegreeParams().isAntiAliased() ) {
828                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
829                                                                     RenderingHints.VALUE_ANTIALIAS_ON );
830                                ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
831                                                                     RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
832                            }
833                        }
834                        map.paint( g );
835                    }
836                } catch ( Exception e ) {
837                    LOG.logError( e.getMessage(), e );
838                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
839                }
840            }
841    
842            // print a copyright note at the left lower corner of the map
843            printCopyright( g, request.getHeight() );
844    
845            if ( mime.equals( "image/svg+xml" ) || mime.equals( "image/svg xml" ) ) {
846                Element root = ( (SVGGraphics2D) g ).getRoot();
847                root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
848                response = WMSProtocolFactory.createGetMapResponse( request, exce, root );
849            } else {
850                response = WMSProtocolFactory.createGetMapResponse( request, exce, target );
851            }
852            g.dispose();
853    
854            return response;
855        }
856    
857        /**
858         * prints a copyright note at left side of the map bottom. The copyright note will be extracted
859         * from the WMS capabilities/configuration
860         * 
861         * @param g
862         *            graphic context of the map
863         * @param heigth
864         *            height of the map in pixel
865         */
866        private void printCopyright( Graphics g, int heigth ) {
867            WMSDeegreeParams dp = configuration.getDeegreeParams();
868            String copyright = dp.getCopyRight();
869            if ( copyrightImg != null ) {
870                g.drawImage( copyrightImg, 8, heigth - copyrightImg.getHeight() - 5, null );
871            } else {
872                if ( copyright != null ) {
873                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
874                    g.setColor( Color.BLACK );
875                    g.drawString( copyright, 8, heigth - 15 );
876                    g.drawString( copyright, 10, heigth - 15 );
877                    g.drawString( copyright, 8, heigth - 13 );
878                    g.drawString( copyright, 10, heigth - 13 );
879                    g.setColor( Color.WHITE );
880                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
881                    g.drawString( copyright, 9, heigth - 14 );
882                }
883            }
884    
885        }
886        
887        /**
888         * @return the request that is being handled
889         */
890        protected GetMap getRequest() {
891            return request;
892        }
893    
894        /**
895         * @return the requests coordinate system
896         */
897        protected CoordinateSystem getRequestCRS() {
898            return reqCRS;
899        }
900        
901        // ///////////////////////////////////////////////////////////////////////////
902        // inner classes //
903        // ///////////////////////////////////////////////////////////////////////////
904    
905        private class MapServiceTask implements Callable<Object[]> {
906    
907            GetMapServiceInvokerForNL invoker;
908    
909            MapServiceTask( GetMapServiceInvokerForNL invoker ) {
910                this.invoker = invoker;
911            }
912    
913            public Object[] call()
914                                    throws Exception {
915                return (Object[]) this.invoker.run();
916            }
917        }
918    
919    
920    }