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