001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/operation/GetMap.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.operation;
037    
038    import static org.deegree.crs.components.Unit.DEGREE;
039    
040    import java.awt.Color;
041    import java.io.Serializable;
042    import java.io.UnsupportedEncodingException;
043    import java.net.MalformedURLException;
044    import java.net.URL;
045    import java.net.URLDecoder;
046    import java.net.URLEncoder;
047    import java.util.ArrayList;
048    import java.util.Arrays;
049    import java.util.HashMap;
050    import java.util.Iterator;
051    import java.util.LinkedList;
052    import java.util.List;
053    import java.util.Map;
054    import java.util.StringTokenizer;
055    
056    import org.deegree.datatypes.values.Values;
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    import org.deegree.framework.util.CharsetUtils;
060    import org.deegree.framework.util.ColorUtils;
061    import org.deegree.framework.util.IDGenerator;
062    import org.deegree.framework.util.MimeTypeMapper;
063    import org.deegree.framework.util.NetWorker;
064    import org.deegree.framework.util.StringTools;
065    import org.deegree.framework.xml.Marshallable;
066    import org.deegree.framework.xml.NamespaceContext;
067    import org.deegree.framework.xml.XMLFragment;
068    import org.deegree.framework.xml.XMLParsingException;
069    import org.deegree.framework.xml.XMLTools;
070    import org.deegree.graphics.sld.SLDFactory;
071    import org.deegree.graphics.sld.StyledLayerDescriptor;
072    import org.deegree.i18n.Messages;
073    import org.deegree.model.crs.CRSFactory;
074    import org.deegree.model.crs.CoordinateSystem;
075    import org.deegree.model.crs.UnknownCRSException;
076    import org.deegree.model.spatialschema.Envelope;
077    import org.deegree.model.spatialschema.GMLGeometryAdapter;
078    import org.deegree.model.spatialschema.GeometryFactory;
079    import org.deegree.ogcbase.CommonNamespaces;
080    import org.deegree.ogcbase.InvalidGMLException;
081    import org.deegree.ogcwebservices.InconsistentRequestException;
082    import org.deegree.ogcwebservices.OGCWebServiceException;
083    import org.deegree.ogcwebservices.wmps.operation.PrintMap;
084    import org.deegree.ogcwebservices.wms.InvalidCRSException;
085    import org.deegree.ogcwebservices.wms.InvalidFormatException;
086    import org.deegree.ogcwebservices.wms.InvalidSRSException;
087    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
088    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
089    import org.w3c.dom.Document;
090    import org.w3c.dom.Element;
091    
092    /**
093     * This interface describes the access to the parameters of a GeMap request. It is excpected that there are two kinds of
094     * request. The first is the 'normal' HTTP GET request with name-value-pair enconding and the second is a HTTP POST
095     * request containing a SLD.
096     * <p>
097     * </p>
098     * Even it is possible to access the values of a HTTP GET request throught their bean accessor methods the request shall
099     * be mapped to a SLD data structure that is accessible using the <tt>getSLD()</tt>.
100     * 
101     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
102     * @author last edited by: $Author:wanhoff$
103     * 
104     * @version $Revision: 21363 $, $Date:20.03.2007$
105     */
106    public class GetMap extends WMSRequestBase {
107    
108        private static final long serialVersionUID = 887256882709344021L;
109    
110        private static final ILogger LOG = LoggerFactory.getLogger( GetMap.class );
111    
112        private Values elevation = null;
113    
114        private Values time = null;
115    
116        private Map<String, Values> sampleDimension = null;
117    
118        private List<Layer> layers = null;
119    
120        private Color bGColor = null;
121    
122        private Envelope boundingBox = null;
123    
124        private String exceptions = null;
125    
126        private String format = null;
127    
128        private String srs = null;
129    
130        private StyledLayerDescriptor sld = null;
131    
132        private URL sLD_URL = null;
133    
134        private URL wFS_URL = null;
135    
136        private boolean transparency = false;
137    
138        private int height = 0;
139    
140        private int width = 0;
141    
142        private DimensionValues dimTime;
143    
144        private DimensionValues dimElev;
145    
146        /**
147         * A list to transport messages to the WMSHandler that need to be set in HTTP headers.
148         */
149        public LinkedList<String> warningHeaders = new LinkedList<String>();
150    
151        /**
152         * creates a <tt>WTSGetViewRequest</tt> from a set of parameters and builds up the complete SLD.
153         * 
154         * @return an instance of <tt>GetMapRequest</tt>
155         * @param version
156         *            Request version.
157         * @param layers
158         *            list of one or more map layers. Optional if SLD parameter is present. Contains list of one rendering
159         *            style per requested layer. Optional if SLD parameter is present.
160         * @param elevation
161         *            Elevation of layer desired.
162         * @param sampleDimension
163         *            Value of other dimensions as appropriate.
164         * @param format
165         *            Output format of map.
166         * @param width
167         *            Width in pixels of map picture.
168         * @param height
169         *            Height in pixels of map picture.
170         * @param srs
171         *            the requested Spatial Reference System.
172         * @param boundingBox
173         *            Bounding box corners (lower left, upper right) in SRS units.
174         * @param transparency
175         *            Background transparency of map.
176         * @param bGColor
177         *            Hexadecimal red-green-blue color value for the background color.
178         * @param exceptions
179         *            The format in which exceptions are to be reported by the WMS.
180         * @param time
181         *            Time value of layer desired
182         * @param sld
183         *            Styled Layer Descriptor
184         * @param id
185         *            an unique ID of the request
186         * @param sldURL
187         * @param vendorSpecificParameter
188         *            Vendor Specific Parameter
189         */
190        public static GetMap create( String version, String id, Layer[] layers, Values elevation,
191                                     Map<String, Values> sampleDimension, String format, int width, int height, String srs,
192                                     Envelope boundingBox, boolean transparency, Color bGColor, String exceptions,
193                                     Values time, URL sldURL, StyledLayerDescriptor sld,
194                                     Map<String, String> vendorSpecificParameter ) {
195            return new GetMap( version, id, layers, elevation, sampleDimension, format, width, height, srs, boundingBox,
196                               transparency, bGColor, exceptions, time, sldURL, sld, vendorSpecificParameter );
197        }
198    
199        /**
200         * creates a getMap request for requesting a cascaded remote WMS considering the getMap request and the
201         * filterconditions defined in the submitted <tt>DataSource</tt> object The request will be encapsualted within a
202         * <tt>OGCWebServiceEvent</tt>.
203         * 
204         * @param ds
205         * @param request
206         * @param style
207         * @param layer
208         * @return GetMap request object
209         */
210        public static GetMap createGetMapRequest( AbstractDataSource ds, GetMap request, String style, String layer ) {
211    
212            GetMap gmr = ( (RemoteWMSDataSource) ds ).getGetMapRequest();
213    
214            String format = request.getFormat();
215    
216            if ( gmr != null && !"%default%".equals( gmr.getFormat() ) ) {
217                format = gmr.getFormat();
218            }
219    
220            GetMap.Layer[] lys = null;
221            lys = new GetMap.Layer[1];
222    
223            if ( style != null ) {
224                lys[0] = PrintMap.createLayer( layer, style );
225            } else {
226                lys[0] = PrintMap.createLayer( layer, "$DEFAULT" );
227            }
228            if ( gmr != null && gmr.getLayers() != null && !( gmr.getLayers()[0].getName().equals( "%default%" ) ) ) {
229                lys = gmr.getLayers();
230            }
231            Color bgColor = request.getBGColor();
232            if ( gmr != null && gmr.getBGColor() != null ) {
233                bgColor = gmr.getBGColor();
234            }
235            Values time = request.getTime();
236            if ( gmr != null && gmr.getTime() != null ) {
237                time = gmr.getTime();
238            }
239    
240            Map<String, String> vendorSpecificParameter = request.getVendorSpecificParameters();
241            if ( gmr != null && gmr.getVendorSpecificParameters() != null && gmr.getVendorSpecificParameters().size() > 0 ) {
242                vendorSpecificParameter.putAll( gmr.getVendorSpecificParameters() );
243            }
244            String version = "1.1.0";
245            if ( gmr != null && gmr.getVersion() != null ) {
246                version = gmr.getVersion();
247            }
248    
249            Values elevation = request.getElevation();
250            if ( gmr != null && gmr.getElevation() != null ) {
251                elevation = gmr.getElevation();
252            }
253            Map<String, Values> sampleDim = null;
254            if ( gmr != null && gmr.getSampleDimension() != null ) {
255                sampleDim = gmr.getSampleDimension();
256            }
257    
258            boolean tranparency = false;
259            if ( gmr != null ) {
260                tranparency = gmr.getTransparency();
261            }
262    
263            // now filter out the unwanted vendor specific parameters and put in
264            // the wanted additional ones
265            Map<String, String> vsp = new HashMap<String, String>( 10 );
266            vsp.putAll( ( (RemoteWMSDataSource) ds ).getAddedParameters() );
267            for ( String name : ( (RemoteWMSDataSource) ds ).getPassedParameters() ) {
268                if ( vendorSpecificParameter.containsKey( name ) ) {
269                    vsp.put( name, vendorSpecificParameter.get( name ).toString() );
270                }
271            }
272    
273            IDGenerator idg = IDGenerator.getInstance();
274            gmr = GetMap.create( version, "" + idg.generateUniqueID(), lys, elevation, sampleDim, format,
275                                 request.getWidth(), request.getHeight(), request.getSrs(), request.getBoundingBox(),
276                                 tranparency, bgColor, request.getExceptions(), time, null, null, vsp );
277    
278            return gmr;
279        }
280    
281        /**
282         * creates a <tt>GetMapRequest</tt> from a <tt>HashMap</tt> that contains the request parameters as key-value-pairs.
283         * Keys are expected to be in upper case notation.
284         * 
285         * @param model
286         *            <tt>HashMap</tt> containing the request parameters
287         * @return an instance of <tt>GetMapRequest</tt>
288         * @throws InconsistentRequestException
289         * @throws XMLParsingException
290         * @throws MalformedURLException
291         */
292        public static GetMap create( Map<String, String> model )
293                                throws InconsistentRequestException, XMLParsingException, MalformedURLException {
294    
295            LOG.logDebug( "Request parameters: " + model );
296    
297            // use model.remove(..) so at the end of the method the vendor
298            // specific parameters remains in the model HashMap
299            model.remove( "REQUEST" );
300    
301            // Version
302            String version = model.remove( "VERSION" );
303    
304            if ( version == null ) {
305                version = model.remove( "WMTVER" );
306            }
307    
308            if ( version == null ) {
309                throw new InconsistentRequestException( "VERSION-value must be set" );
310            }
311    
312            // LAYERS & STYLES & SLD (URL, XML)
313            StyledLayerDescriptor sld = null;
314            String sld_body = model.remove( "SLD_BODY" );
315            String sld_urlstring = model.remove( "SLD" );
316    
317            // The SLD is complete in the Maprequest
318            URL sLD_URL = null;
319    
320            if ( sld_body != null ) {
321                try {
322                    sld_body = URLDecoder.decode( sld_body, CharsetUtils.getSystemCharset() );
323                    sld = SLDFactory.createSLD( sld_body );
324                } catch ( Exception ee ) {
325                    throw new XMLParsingException( "Could not decode SLD_BODY: " + ee.toString() );
326                }
327            } else if ( sld_urlstring != null ) {
328                // The SLD is as url in the Maprequest
329                sLD_URL = new URL( sld_urlstring );
330    
331                try {
332                    sld = SLDFactory.createSLD( sLD_URL );
333                } catch ( Exception ioex ) {
334                    ioex.printStackTrace();
335                    LOG.logError( ioex.getMessage(), ioex );
336                    throw new InconsistentRequestException( "IOException occured during the access "
337                                                            + "to the SLD-URL. Wrong URL? Server down?" + ioex.getMessage() );
338                }
339            }
340    
341            // LAYERS & STYLES
342            String layersstring = model.remove( "LAYERS" );
343            if ( ( layersstring == null || layersstring.trim().length() == 0 ) && ( sld == null ) ) {
344                throw new InconsistentRequestException( "At least one layer must be defined within a GetMap request" );
345            }
346            String stylesstring = model.remove( "STYLES" );
347    
348            // normalize styles parameter
349            if ( stylesstring == null ) {
350                stylesstring = "";
351            }
352            if ( stylesstring.startsWith( "," ) ) {
353                stylesstring = "$DEFAULT" + stylesstring;
354            }
355    
356            stylesstring = StringTools.replace( stylesstring, ",,", ",$DEFAULT,", true );
357    
358            if ( stylesstring.endsWith( "," ) ) {
359                stylesstring = stylesstring + "$DEFAULT";
360            }
361    
362            List<String> layers = StringTools.toList( layersstring, ",", false );
363    
364            List<String> styles = null;
365            if ( stylesstring.length() == 0 ) {
366                styles = new ArrayList<String>( layers.size() );
367                for ( int i = 0; i < layers.size(); i++ ) {
368                    styles.add( "$DEFAULT" );
369                }
370            } else {
371                styles = StringTools.toList( stylesstring, ",", false );
372            }
373    
374            // At last, build up the Layer object
375            GetMap.Layer[] ls = new GetMap.Layer[layers.size()];
376    
377            if ( styles.size() != layers.size() ) {
378                throw new InconsistentRequestException( Messages.getMessage( "WMS_WRONG_STYLES" ) );
379            }
380    
381            for ( int i = 0; i < layers.size(); i++ ) {
382                try {
383                    String l = URLDecoder.decode( layers.get( i ), CharsetUtils.getSystemCharset() );
384                    ls[i] = GetMap.createLayer( l, styles.get( i ) );
385                } catch ( UnsupportedEncodingException e2 ) {
386                    e2.printStackTrace();
387                }
388            }
389    
390            // FORMAT
391            String format = model.remove( "FORMAT" );
392            if ( format == null ) {
393                throw new InconsistentRequestException( "FORMAT-value must be set" );
394            }
395            try {
396                format = URLDecoder.decode( format, CharsetUtils.getSystemCharset() );
397            } catch ( UnsupportedEncodingException e1 ) {
398                e1.printStackTrace();
399            }
400            if ( !MimeTypeMapper.isKnownImageType( format ) ) {
401                throw new InvalidFormatException( format + " is not a valid image/result format" );
402            }
403    
404            // width
405            String tmp = model.remove( "WIDTH" );
406            if ( tmp == null ) {
407                throw new InconsistentRequestException( "WIDTH must be set" );
408            }
409            int width = 0;
410            try {
411                width = Integer.parseInt( tmp );
412            } catch ( Exception e ) {
413                throw new InconsistentRequestException( "WIDTH must be a valid integer number" );
414            }
415    
416            // height
417            tmp = model.remove( "HEIGHT" );
418            if ( tmp == null ) {
419                throw new InconsistentRequestException( "HEIGHT must be set" );
420            }
421            int height = 0;
422            try {
423                height = Integer.parseInt( tmp );
424            } catch ( Exception e ) {
425                throw new InconsistentRequestException( "HEIGHT must be a valid integer number" );
426            }
427    
428            double minx, miny, maxx, maxy;
429            Envelope boundingBox = null;
430            String srs;
431            boolean isLongLat = false;
432    
433            boolean is130 = false;
434    
435            if ( "1.3.0".compareTo( version ) <= 0 ) {
436                is130 = true;
437    
438                // SRS or rather CRS
439                srs = model.remove( "CRS" );
440                if ( srs == null ) {
441                    throw new InvalidCRSException( "CRS-value must be set" );
442                }
443    
444                // check for geographic coordinate system
445                try {
446                    CoordinateSystem crs = CRSFactory.create( srs );
447                    isLongLat = crs.getAxisUnits()[0].equals( DEGREE ) && srs.toLowerCase().startsWith( "epsg" );
448                } catch ( UnknownCRSException e ) {
449                    LOG.logDebug( e.getLocalizedMessage(), e );
450                    throw new InvalidCRSException( srs );
451                }
452    
453            } else {
454                // SRS
455                srs = model.remove( "SRS" );
456                if ( srs == null ) {
457                    throw new InvalidSRSException( "SRS-value must be set" );
458                }
459    
460                try {
461                    // check for crs validity - yes, this method is bad. Is there a better one?
462                    CRSFactory.create( srs );
463                } catch ( UnknownCRSException e ) {
464                    LOG.logDebug( e.getLocalizedMessage(), e );
465                    if ( is130 ) {
466                        throw new InvalidCRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", srs ) );
467                    }
468                    throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", srs ) );
469                }
470    
471            }
472    
473            // BBOX
474            String boxstring = model.remove( "BBOX" );
475            if ( boxstring == null ) {
476                throw new InconsistentRequestException( "BBOX-value must be set" );
477            }
478    
479            // ESRI ArcGIS Explorer hack which sends bboxes like 45,1234,7,234,46,123,8,345
480            int count = StringTools.countString( boxstring, "," );
481            if ( count == 7 ) {
482                String str = boxstring;
483                boxstring = "";
484                for ( int i = 0; i < 4; ++i ) {
485                    str = str.replaceFirst( ",", "." );
486                    if ( str.indexOf( "," ) == -1 ) {
487                        boxstring += str;
488                    } else {
489                        boxstring += str.substring( 0, str.indexOf( "," ) + 1 );
490                        str = str.substring( str.indexOf( "," ) + 1 );
491                    }
492                }
493                LOG.logDebug( "Changed bbox string from ArcGIS Explorer to " + boxstring );
494            }
495    
496            StringTokenizer st = new StringTokenizer( boxstring, "," );
497    
498            if ( isLongLat ) {
499                // parse first y, then x
500                String s = st.nextToken().replace( ' ', '+' );
501                miny = Double.parseDouble( s );
502                s = st.nextToken().replace( ' ', '+' );
503                minx = Double.parseDouble( s );
504                s = st.nextToken().replace( ' ', '+' );
505                maxy = Double.parseDouble( s );
506                s = st.nextToken().replace( ' ', '+' );
507                maxx = Double.parseDouble( s );
508            } else {
509                // old method
510                String s = st.nextToken().replace( ' ', '+' );
511                minx = Double.parseDouble( s );
512                s = st.nextToken().replace( ' ', '+' );
513                miny = Double.parseDouble( s );
514                s = st.nextToken().replace( ' ', '+' );
515                maxx = Double.parseDouble( s );
516                s = st.nextToken().replace( ' ', '+' );
517                maxy = Double.parseDouble( s );
518            }
519    
520            // check for consistency
521            if ( minx >= maxx ) {
522                throw new InvalidFormatException( "minx must be lower than maxx" );
523            }
524    
525            if ( miny >= maxy ) {
526                throw new InvalidFormatException( "miny must be lower than maxy" );
527            }
528    
529            boundingBox = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null );
530    
531            // TRANSPARENCY
532            boolean transparency = false;
533            String tp = model.remove( "TRANSPARENT" );
534            if ( tp != null ) {
535                transparency = tp.toUpperCase().trim().equals( "TRUE" );
536            }
537    
538            String mime = MimeTypeMapper.toMimeType( format );
539            if ( mime.equals( "image/jpg" ) || mime.equals( "image/jpeg" ) || mime.equals( "image/bmp" )
540                 || mime.equals( "image/tif" ) || mime.equals( "image/tiff" ) ) {
541                transparency = false;
542            }
543    
544            // BGCOLOR
545            tmp = model.remove( "BGCOLOR" );
546            Color bgColor = Color.white;
547            if ( tmp != null ) {
548                bgColor = Color.decode( tmp );
549            }
550    
551            // EXCEPTIONS
552            String exceptions = model.remove( "EXCEPTIONS" );
553    
554            if ( exceptions == null ) {
555                if ( is130 ) {
556                    exceptions = "XML";
557                } else {
558                    exceptions = "application/vnd.ogc.se_xml";
559                }
560            }
561    
562            // WFS
563            /*
564             * URL wFS_URL = null; if ((String)model.get( "WFS" ) != null) { wFS_URL = new URL((String)model.remove( "WFS"
565             * )); }
566             */
567    
568            // ID
569            String id = model.remove( "ID" );
570            if ( id == null ) {
571                throw new InconsistentRequestException( "ID-value must be set" );
572            }
573    
574            String dim = model.remove( "TIME" );
575            DimensionValues dimTime = dim == null ? null : new DimensionValues( dim );
576            dim = model.remove( "ELEVATION" );
577            DimensionValues dimElev = dim == null ? null : new DimensionValues( dim );
578    
579            // VendorSpecificParameter; because all defined parameters has been
580            // removed
581            // from the model the vendorSpecificParameters are what left
582            Map<String, String> vendorSpecificParameter = new HashMap<String, String>();
583            for ( Object str : model.keySet() ) {
584                vendorSpecificParameter.put( str.toString(), model.get( str ).toString() );
585            }
586    
587            return new GetMap( version, id, ls, format, width, height, srs, boundingBox, transparency, bgColor, exceptions,
588                               sLD_URL, sld, vendorSpecificParameter, dimTime, dimElev );
589        }
590    
591        /**
592         * creates a <tt>GetMapRequest</tt> from its XML representation as defined in SLD 1.0.0 specification
593         * 
594         * <p>
595         * This method does not yet cope with 1.3.0.
596         * </p>
597         * 
598         * @param id
599         *            an unique id of the request
600         * @param doc
601         *            the document tree
602         * @return an instance of <tt>GetMapRequest</tt>
603         * @throws XMLParsingException
604         * @throws InvalidSRSException
605         * @throws InconsistentRequestException
606         */
607        public static GetMap create( String id, Document doc )
608                                throws XMLParsingException, InvalidSRSException, InconsistentRequestException {
609            String PSLD = CommonNamespaces.SLD_PREFIX + ':';
610    
611            Element root = doc.getDocumentElement();
612            NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
613            Element sldElem = (Element) XMLTools.getRequiredNode( root, PSLD + "StyledLayerDescriptor", nsContext );
614            XMLFragment xml = new XMLFragment();
615            xml.setRootElement( sldElem );
616    
617            StyledLayerDescriptor sld = SLDFactory.createSLD( xml );
618            String version = root.getAttribute( "version" );
619    
620            boolean is130 = false;
621    
622            if ( "1.3.0".compareTo( version ) <= 0 ) {
623                is130 = true;
624            }
625    
626            Element bboxElem = (Element) XMLTools.getRequiredNode( root, PSLD + "BoundingBox", nsContext );
627    
628            Envelope bbox;
629            try {
630                bbox = GMLGeometryAdapter.wrapBox( bboxElem, null );
631                // check for consistency
632                if ( bbox.getMin().getX() >= bbox.getMax().getX() ) {
633                    throw new InvalidFormatException( "minx must be lower than maxx" );
634                }
635    
636                if ( bbox.getMin().getY() >= bbox.getMax().getY() ) {
637                    throw new InvalidFormatException( "miny must be lower than maxy" );
638                }
639            } catch ( InvalidGMLException e ) {
640                LOG.logDebug( e.getLocalizedMessage(), e );
641                if ( bboxElem == null ) {
642                    throw new InconsistentRequestException( Messages.getMessage( "WMS_NO_BOUNDINGBOX" ) );
643                }
644                if ( is130 ) {
645                    throw new InvalidCRSException( Messages.getMessage( "WMS_UNKNOWN_CRS",
646                                                                        bboxElem.getAttribute( "srsName" ) ) );
647                }
648                throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", bboxElem.getAttribute( "srsName" ) ) );
649            } catch ( UnknownCRSException e ) {
650                LOG.logDebug( e.getLocalizedMessage(), e );
651                if ( bboxElem == null ) {
652                    throw new InconsistentRequestException( Messages.getMessage( "WMS_NO_BOUNDINGBOX" ) );
653                }
654                if ( is130 ) {
655                    throw new InvalidCRSException( Messages.getMessage( "WMS_UNKNOWN_CRS",
656                                                                        bboxElem.getAttribute( "srsName" ) ) );
657                }
658                throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", bboxElem.getAttribute( "srsName" ) ) );
659            }
660    
661            String srs = bbox.getCoordinateSystem().getIdentifier().toString();
662    
663            Element output = (Element) XMLTools.getRequiredNode( root, PSLD + "Output", nsContext );
664    
665            boolean transparent = XMLTools.getNodeAsBoolean( output, PSLD + "Transparent", nsContext, false );
666    
667            int width = 0;
668            int height = 0;
669            try {
670                Element node = (Element) XMLTools.getRequiredNode( output, PSLD + "Size", nsContext );
671                width = XMLTools.getRequiredNodeAsInt( node, PSLD + "Width", nsContext );
672                height = XMLTools.getRequiredNodeAsInt( node, PSLD + "Height", nsContext );
673            } catch ( XMLParsingException e ) {
674                throw new InconsistentRequestException( Messages.getMessage( "WMS_REQUEST_SIZE" ) );
675            }
676    
677            String exception = XMLTools.getNodeAsString( output, PSLD + "Exceptions", nsContext,
678                                                         "application/vnd.ogc.se_xml" );
679            String sbgColor = XMLTools.getNodeAsString( output, PSLD + "BGColor", nsContext, "#FFFFFF" );
680            Color bgColor = Color.decode( sbgColor );
681    
682            String format = XMLTools.getRequiredNodeAsString( output, PSLD + "Format", nsContext );
683            if ( format == null ) {
684                throw new InconsistentRequestException( "FORMAT-value must be set" );
685            }
686            try {
687                format = URLDecoder.decode( format, CharsetUtils.getSystemCharset() );
688            } catch ( UnsupportedEncodingException e1 ) {
689                e1.printStackTrace();
690            }
691            if ( !MimeTypeMapper.isKnownImageType( format ) ) {
692                throw new InvalidFormatException( format + " is not a valid image/result format" );
693            }
694    
695            GetMap req = new GetMap( version, id, null, null, null, format, width, height, srs, bbox, transparent, bgColor,
696                                     exception, null, null, sld, null );
697    
698            return req;
699        }
700    
701        /**
702         * Creates a new GetMapRequest object.
703         * 
704         * @param version
705         * @param id
706         * @param layers
707         * @param elevation
708         * @param sampleDimension
709         * @param format
710         * @param width
711         * @param height
712         * @param srs
713         * @param boundingBox
714         * @param transparency
715         * @param bGColor
716         * @param exceptions
717         * @param time
718         * @param sldURL
719         * @param sld
720         * @param vendorSpecificParameter
721         * 
722         */
723        GetMap( String version, String id, Layer[] layers, Values elevation, Map<String, Values> sampleDimension,
724                String format, int width, int height, String srs, Envelope boundingBox, boolean transparency,
725                Color bGColor, String exceptions, Values time, URL sldURL, StyledLayerDescriptor sld,
726                Map<String, String> vendorSpecificParameter ) {
727            super( version, id, vendorSpecificParameter );
728    
729            if ( layers != null ) {
730                this.layers = Arrays.asList( layers );
731            } else {
732                this.layers = new ArrayList<Layer>();
733            }
734            this.sld = sld;
735            this.elevation = elevation;
736            this.sampleDimension = sampleDimension;
737            this.format = format;
738            this.width = width;
739            this.height = height;
740            this.srs = srs;
741            this.boundingBox = boundingBox;
742            this.transparency = transparency;
743            this.bGColor = bGColor;
744            this.exceptions = exceptions;
745            this.time = time;
746            this.sLD_URL = sldURL;
747            // setWFS_URL( wFS_URL );
748        }
749    
750        /**
751         * @param version
752         * @param id
753         * @param ls
754         * @param format
755         * @param width
756         * @param height
757         * @param srs
758         * @param boundingBox
759         * @param transparency
760         * @param bgColor
761         * @param exceptions
762         * @param sld_url
763         * @param sld
764         * @param vendorSpecificParameter
765         * @param dimTime
766         * @param dimElev
767         */
768        public GetMap( String version, String id, Layer[] ls, String format, int width, int height, String srs,
769                       Envelope boundingBox, boolean transparency, Color bgColor, String exceptions, URL sld_url,
770                       StyledLayerDescriptor sld, Map<String, String> vendorSpecificParameter, DimensionValues dimTime,
771                       DimensionValues dimElev ) {
772            this( version, id, ls, null, null, format, width, height, srs, boundingBox, transparency, bgColor, exceptions,
773                  null, sld_url, sld, vendorSpecificParameter );
774            this.dimTime = dimTime;
775            this.dimElev = dimElev;
776        }
777    
778        /**
779         * The FORMAT parameter specifies the output format of the response to an operation.
780         * <p>
781         * </p>
782         * An OGC Web CapabilitiesService may offer only a subset of the formats known for that type of operation, but the
783         * server shall advertise in its Capabilities XML those formats it does support and shall accept requests for any
784         * format it advertises. A CapabilitiesService Instance may optionally offer a new format not previously offered by
785         * other instances, with the recognition that clients are not required to accept or process an unknown format. If a
786         * request contains a Format not offered by a particular server, the server shall throw a CapabilitiesService
787         * Exception (with code "InvalidFormat").
788         * 
789         * @return the output format
790         */
791        public String getFormat() {
792            return format;
793        }
794    
795        /**
796         * sets the format
797         * 
798         * @param format
799         *            the requested output-format
800         */
801        public void setFormat( String format ) {
802            this.format = format;
803        }
804    
805        /**
806         * The required LAYERS parameter lists the map layer(s) to be returned by this GetMap request. The value of the
807         * LAYERS parameter is a comma-separated list of one or more valid layer names. Allowed layer names are the
808         * character data content of any <Layer><Name> element in the Capabilities XML.
809         * <p>
810         * </p>
811         * A WMS shall render the requested layers by drawing the leftmost in the list bottommost, the next one over that,
812         * and so on.
813         * <p>
814         * </p>
815         * Each layer is associated to a style. Styles are also is encoded as a comma- seperated list within the GetMap
816         * request.
817         * <p>
818         * </p>
819         * The required STYLES parameter lists the style in which each layer is to be rendered. There is a one-to-one
820         * correspondence between the values in the LAYERS parameter and the values in the STYLES parameter. Because of this
821         * layer-style combinations are returned coupled within an array of Layer- objects. Each map in the list of LAYERS
822         * is drawn using the corresponding style in the same position in the list of STYLES. Each style Name shall be one
823         * that was defined in the <Name> element of a <Style> element that is either directly contained within, or
824         * inherited by, the associated <Layer> element in Capabilities XML.
825         * 
826         * @return The required LAYERS
827         */
828        public Layer[] getLayers() {
829            return layers.toArray( new Layer[layers.size()] );
830        }
831    
832        /**
833         * adds the &lt;Layer&gt;
834         * 
835         * @param layers
836         */
837        public void addLayers( Layer layers ) {
838            this.layers.add( layers );
839        }
840    
841        /**
842         * sets the &lt;Layer&gt;
843         * 
844         * @param layers
845         *            a set of layer
846         */
847        public void setLayers( Layer[] layers ) {
848            this.layers.clear();
849    
850            if ( layers != null ) {
851                for ( int i = 0; i < layers.length; i++ ) {
852                    this.layers.add( layers[i] );
853                }
854            }
855        }
856    
857        /**
858         * The required SRS parameter states which Spatial Reference System applies to the values in the BBOX parameter. The
859         * value of the SRS parameter shall be one of the values defined in the character data section of an <SRS> element
860         * defined or inherited by the requested layer. The same SRS applies to all layers in a single request.
861         * <p>
862         * </p>
863         * If the WMS server has declared SRS=NONE for a Layer, as discussed in the Basic CapabilitiesService Elements
864         * section, then the Layer does not have a well-defined spatial reference system and should not be shown in
865         * conjunction with other layers. The Client shall specify SRS=NONE (case-insensitive) in the GetMap request and the
866         * Server may issue a CapabilitiesService Exception otherwise.
867         * 
868         * @return the spatial reference system
869         */
870        public String getSrs() {
871            return srs;
872        }
873    
874        /**
875         * sets the srs
876         * 
877         * @param srs
878         *            the spatial reference system
879         */
880        public void setSrs( String srs ) {
881            this.srs = srs;
882        }
883    
884        /**
885         * The required BBOX parameter allows a Client to request a particular Bounding Box. Bounding Boxes are defined in
886         * the Basic CapabilitiesService Elements section. The value of the BBOX parameter in a GetMap request is a list of
887         * comma-separated numbers of the form "minx,miny,maxx,maxy".
888         * <p>
889         * </p>
890         * If the WMS server has declared that a Layer is not subsettable then the Client shall specify exactly the declared
891         * Bounding Box values in the GetMap request and the Server may issue a CapabilitiesService Exception otherwise.
892         * 
893         * @return the bounding box
894         */
895        public Envelope getBoundingBox() {
896            return boundingBox;
897        }
898    
899        /**
900         * @see #getBoundingBox()
901         * @param boundingBox
902         */
903        public void setBoundingBox( Envelope boundingBox ) {
904            this.boundingBox = boundingBox;
905        }
906    
907        /**
908         * WIDTH specifies the number of pixels to be used between the minimum and maximum X values (inclusive) in the BBOX
909         * parameter. The returned picture, regardless of its return format, shall have exactly the specified width and
910         * height in pixels. In the case where the aspect ratio of the BBOX and the ratio width/height are different, the
911         * WMS shall stretch the returned map so that the resulting pixels could themselves be rendered in the aspect ratio
912         * of the BBOX. In other words, it should be possible using this definition to request a map for a device whose
913         * output pixels are themselves non-square, or to stretch a map into an image area of a different aspect ratio.
914         * 
915         * @return the width
916         */
917        public int getWidth() {
918            return width;
919        }
920    
921        /**
922         * @see #getWidth()
923         * @param width
924         */
925        public void setWidth( int width ) {
926            this.width = width;
927        }
928    
929        /**
930         * HEIGHT specifies the number of pixels between the minimum and maximum Y values. The returned picture, regardless
931         * of its return format, shall have exactly the specified width and height in pixels. In the case where the aspect
932         * ratio of the BBOX and the ratio width/height are different, the WMS shall stretch the returned map so that the
933         * resulting pixels could themselves be rendered in the aspect ratio of the BBOX. In other words, it should be
934         * possible using this definition to request a map for a device whose output pixels are themselves non-square, or to
935         * stretch a map into an image area of a different aspect ratio.
936         * 
937         * @return the height
938         */
939        public int getHeight() {
940            return height;
941        }
942    
943        /**
944         * @see #getHeight()
945         * @param height
946         */
947        public void setHeight( int height ) {
948            this.height = height;
949        }
950    
951        /**
952         * The optional TRANSPARENT parameter specifies whether the map background is to be made transparent or not.
953         * TRANSPARENT can take on two values, "TRUE" or "FALSE". The default value is FALSE if this parameter is absent
954         * from the request.
955         * <p>
956         * </p>
957         * The ability to return pictures drawn with transparent pixels allows results of different Map requests to be
958         * overlaid, producing a composite map. It is strongly recommended that every WMS offer a format that provides
959         * transparency for layers which could sensibly be overlaid above others.
960         * 
961         * @return the transparency setting
962         */
963        public boolean getTransparency() {
964            return transparency;
965        }
966    
967        /**
968         * The optional BGCOLOR parameter specifies the color to be used as the background of the map. The general format of
969         * BGCOLOR is a hexadecimal encoding of an RGB value where two hexadecimal characters are used for each of Red,
970         * Green, and Blue color values. The values can range between 00 and FF for each (0 and 255, base 10). The format is
971         * 0xRRGGBB; either upper or lower case characters are allowed for RR, GG, and BB values. The "0x" prefix shall have
972         * a lower case 'x'. The default value is 0xFFFFFF (corresponding to the color white) if this parameter is absent
973         * from the request.
974         * 
975         * @return the background color
976         */
977        public Color getBGColor() {
978            return bGColor;
979        }
980    
981        /**
982         * The optional EXCEPTIONS parameter states the manner in which errors are to be reported to the client. The default
983         * value is application/vnd.ogc.se_xml if this parameter is absent from the request.
984         * <p>
985         * </p>
986         * A Web Map CapabilitiesService shall offer one or more of the following exception reporting formats by listing
987         * them in separate <Format> elements inside the <Exceptions> element of its Capabilities XML. The entire MIME type
988         * string in <Format> is used as the value of the EXCEPTIONS parameter. The first of these formats is required to be
989         * offered by every WMS; the others are optional.
990         * 
991         * @return the exceptions parameter
992         */
993        public String getExceptions() {
994            return exceptions;
995        }
996    
997        /**
998         * This specification is based on [ISO 8601:1988(E)]; it extends ISO 8601 in the following ways:
999         * <UL>
1000         * <li>It defines a syntax for expressing the start, end and periodicity of a data collection.
1001         * <li>It defines terms to represent the 7 days of the week.
1002         * <li>It allows years before 0001 AD.
1003         * <li>It allows times in the distant geologic past (thousands, millions or billions of years before present).
1004         * </UL>
1005         * 
1006         * @return the time setting
1007         */
1008        public Values getTime() {
1009            return time;
1010        }
1011    
1012        /**
1013         * Some geospatial information may be available at multiple elevations. An OWS may announce available elevations in
1014         * its Capabilities XML, and some operations include a parameter for requesting a particular elevation. A single
1015         * elevation value is an integer or real number whose units are declared by naming an EPSG datum. When providing
1016         * elevation information, Servers should declare a default value in Capabilities XML unless there is compelling
1017         * reason to behave otherwise, and Servers shall respond with the default value if one has been declared and the
1018         * Client request does not include a value.
1019         * 
1020         * @return the elevation
1021         */
1022        public Values getElevation() {
1023            return elevation;
1024        }
1025    
1026        /**
1027         * Some geospatial information may be available at other dimensions (for example, satellite images in different
1028         * wavelength bands). The dimensions other than the four space-time dimensions are referred to as
1029         * "sample dimensions". An OWS may announce available sample dimensions in its Capabilities XML, and some operations
1030         * include a mechanism for including dimensional parameters. Each sample dimension has a Name and one or more valid
1031         * values.
1032         * 
1033         * @return the map
1034         */
1035        public Map<String, Values> getSampleDimension() {
1036            return sampleDimension;
1037        }
1038    
1039        /**
1040         * @return the URL of Styled Layer Descriptor (as defined in SLD Specification). This parameter is optional. If no
1041         *         sld URL is defined <tt>null</tt> will be returned.
1042         */
1043        public URL getSLD_URL() {
1044            return sLD_URL;
1045        }
1046    
1047        /**
1048         * @return the URL of Web Feature CapabilitiesService providing features to be symbolized using SLD. This parameter
1049         *         is optional. If no WFS URL is defined <tt>null</tt> will be returned.
1050         */
1051        public URL getWFS_URL() {
1052            return wFS_URL;
1053        }
1054    
1055        /**
1056         * @return the SLD the request is made of. This implies that a 'simple' HTTP GET-Request will be transformed into a
1057         *         valid SLD. This is mandatory within a JaGo WMS.
1058         *         <p>
1059         *         </p>
1060         *         This mean even if a GetMap request is send using the HTTP GET method, an implementing class has to map
1061         *         the request to a SLD data structure.
1062         */
1063        public StyledLayerDescriptor getStyledLayerDescriptor() {
1064            return sld;
1065        }
1066    
1067        /**
1068         * @return the parameter of a HTTP GET request.
1069         * 
1070         */
1071        @Override
1072        public String getRequestParameter()
1073                                throws OGCWebServiceException {
1074    
1075            // indicates if the request parameters are decoded as SLD. deegree won't
1076            // perform SLD requests through HTTP GET
1077            if ( boundingBox == null ) {
1078                throw new OGCWebServiceException( "Operations can't be expressed as HTTP GET request " );
1079            }
1080    
1081            StringBuffer sb = new StringBuffer();
1082    
1083            if ( getVersion().compareTo( "1.0.0" ) <= 0 ) {
1084                sb.append( "VERSION=" ).append( getVersion() ).append( "&REQUEST=map" );
1085                String f = StringTools.replace( getFormat(), "image/", "", false );
1086                sb.append( "&FORMAT=" );
1087                try {
1088                    sb.append( URLEncoder.encode( f, CharsetUtils.getSystemCharset() ) );
1089                } catch ( UnsupportedEncodingException e ) {
1090                    // system encoding should be supported...
1091                }
1092            } else {
1093                sb.append( "&VERSION=" ).append( getVersion() ).append( "&REQUEST=GetMap" );
1094                sb.append( "&FORMAT=" );
1095                try {
1096                    sb.append( URLEncoder.encode( getFormat(), CharsetUtils.getSystemCharset() ) );
1097                } catch ( UnsupportedEncodingException e ) {
1098                    // system encoding should be supported...
1099                }
1100            }
1101    
1102            sb.append( "&TRANSPARENT=" ).append( Boolean.toString( getTransparency() ).toUpperCase() );
1103            sb.append( "&WIDTH=" ).append( getWidth() );
1104            sb.append( "&HEIGHT=" ).append( getHeight() );
1105            sb.append( "&EXCEPTIONS=" ).append( getExceptions() );
1106            sb.append( "&BGCOLOR=" ).append( ColorUtils.toHexCode( "0x", bGColor ) );
1107    
1108            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
1109                sb.append( "&BBOX=" ).append( boundingBox.getMin().getY() );
1110                sb.append( ',' ).append( boundingBox.getMin().getX() );
1111                sb.append( ',' ).append( boundingBox.getMax().getY() );
1112                sb.append( ',' ).append( boundingBox.getMax().getX() );
1113            } else {
1114                sb.append( "&BBOX=" ).append( boundingBox.getMin().getX() );
1115                sb.append( ',' ).append( boundingBox.getMin().getY() );
1116                sb.append( ',' ).append( boundingBox.getMax().getX() );
1117                sb.append( ',' ).append( boundingBox.getMax().getY() );
1118            }
1119    
1120            Layer[] layers = getLayers();
1121            StringBuffer l = new StringBuffer( 500 );
1122            StringBuffer s = new StringBuffer( 500 );
1123    
1124            if ( sLD_URL == null ) {
1125                for ( int i = 0; i < layers.length; i++ ) {
1126                    try {
1127                        l.append( URLEncoder.encode( layers[i].getName(), CharsetUtils.getSystemCharset() ) );
1128                        l.append( ',' );
1129                        if ( !layers[i].getStyleName().equals( "$DEFAULT" ) ) {
1130                            s.append( URLEncoder.encode( layers[i].getStyleName(), CharsetUtils.getSystemCharset() ) );
1131                        }
1132                        s.append( ',' );
1133                    } catch ( Exception e ) {
1134                        throw new OGCWebServiceException( e.toString() );
1135                    }
1136                }
1137    
1138                if ( l.length() != 0 ) {
1139                    sb.append( "&LAYERS=" ).append( l.substring( 0, l.length() - 1 ) );
1140                }
1141    
1142                if ( s.length() != 0 ) {
1143                    sb.append( "&STYLES=" ).append( s.substring( 0, s.length() - 1 ) );
1144                }
1145            } else if ( sLD_URL != null ) {
1146                sb.append( "&SLD=" ).append( NetWorker.url2String( sLD_URL ) );
1147            } else if ( sld != null ) {
1148                String tmp = ( (Marshallable) sld ).exportAsXML();
1149                try {
1150                    tmp = URLEncoder.encode( tmp, CharsetUtils.getSystemCharset() );
1151                } catch ( Exception e ) {
1152                    throw new OGCWebServiceException( e.toString() );
1153                }
1154                sb.append( "&SLD_BODY=" ).append( tmp );
1155            }
1156    
1157            if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
1158                sb.append( "&CRS=" ).append( getSrs() );
1159            } else {
1160                sb.append( "&SRS=" ).append( getSrs() );
1161            }
1162    
1163            // TODO
1164            // append time, elevation and sampleDimensions
1165    
1166            if ( getVendorSpecificParameters() != null ) {
1167                Iterator<String> iterator = getVendorSpecificParameters().keySet().iterator();
1168                while ( iterator.hasNext() ) {
1169                    String key = iterator.next();
1170                    String value = getVendorSpecificParameters().get( key );
1171                    try {
1172                        value = URLEncoder.encode( value, CharsetUtils.getSystemCharset() );
1173                    } catch ( UnsupportedEncodingException e ) {
1174                        // system encoding should be supported...
1175                    }
1176                    sb.append( '&' ).append( key ).append( '=' ).append( value );
1177                }
1178            }
1179    
1180            return sb.toString();
1181        }
1182    
1183        @Override
1184        public String toString() {
1185            String s = "An unknown " + this.getClass().getName() + " request";
1186            try {
1187                s = getRequestParameter();
1188            } catch ( OGCWebServiceException e ) {
1189                // in that case, we just don't have the parameters...
1190            }
1191            return s;
1192        }
1193    
1194        /**
1195         * creates a Layer object beacuse of the inner class construct.
1196         * 
1197         * @param name
1198         *            the name of the layer
1199         * @param style
1200         *            the corresponding style of the layer
1201         * @return Layer a layer object constaining name and style
1202         */
1203        public static Layer createLayer( String name, String style ) {
1204            return new Layer( name, style );
1205        }
1206    
1207        /**
1208         * A Layer object. It contains the name of the layer and the corresponding style.
1209         * 
1210         * @version $Revision: 21363 $
1211         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1212         */
1213        public static class Layer implements Serializable {
1214    
1215            private static final long serialVersionUID = -98575941104285931L;
1216    
1217            private String name = null;
1218    
1219            private String styleName = null;
1220    
1221            /**
1222             * constructor initializing the class with the <Layer>
1223             * 
1224             * @param name
1225             * @param styleName
1226             */
1227            public Layer( String name, String styleName ) {
1228                this.name = name;
1229                this.styleName = styleName;
1230            }
1231    
1232            /**
1233             * @return the <Name>
1234             */
1235            public String getName() {
1236                return name;
1237            }
1238    
1239            /**
1240             * @return the <StyleName>
1241             */
1242            public String getStyleName() {
1243                return styleName;
1244            }
1245    
1246        }
1247    
1248        /**
1249         * @return the dimTime
1250         */
1251        public DimensionValues getDimTime() {
1252            return dimTime;
1253        }
1254    
1255        /**
1256         * @param dimTime
1257         *            the dimTime to set
1258         */
1259        public void setDimTime( DimensionValues dimTime ) {
1260            this.dimTime = dimTime;
1261        }
1262    
1263        /**
1264         * @return the dimElev
1265         */
1266        public DimensionValues getDimElev() {
1267            return dimElev;
1268        }
1269    
1270        /**
1271         * @param dimElev
1272         *            the dimElev to set
1273         */
1274        public void setDimElev( DimensionValues dimElev ) {
1275            this.dimElev = dimElev;
1276        }
1277    
1278    }