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 <Layer>
817 *
818 * @param layers
819 */
820 public void addLayers( Layer layers ) {
821 this.layers.add( layers );
822 }
823
824 /**
825 * sets the <Layer>
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 }