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