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