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