001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: 21363 $, $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
479 // ESRI ArcGIS Explorer hack which sends bboxes like 45,1234,7,234,46,123,8,345
480 int count = StringTools.countString( boxstring, "," );
481 if ( count == 7 ) {
482 String str = boxstring;
483 boxstring = "";
484 for ( int i = 0; i < 4; ++i ) {
485 str = str.replaceFirst( ",", "." );
486 if ( str.indexOf( "," ) == -1 ) {
487 boxstring += str;
488 } else {
489 boxstring += str.substring( 0, str.indexOf( "," ) + 1 );
490 str = str.substring( str.indexOf( "," ) + 1 );
491 }
492 }
493 LOG.logDebug( "Changed bbox string from ArcGIS Explorer to " + boxstring );
494 }
495
496 StringTokenizer st = new StringTokenizer( boxstring, "," );
497
498 if ( isLongLat ) {
499 // parse first y, then x
500 String s = st.nextToken().replace( ' ', '+' );
501 miny = Double.parseDouble( s );
502 s = st.nextToken().replace( ' ', '+' );
503 minx = Double.parseDouble( s );
504 s = st.nextToken().replace( ' ', '+' );
505 maxy = Double.parseDouble( s );
506 s = st.nextToken().replace( ' ', '+' );
507 maxx = Double.parseDouble( s );
508 } else {
509 // old method
510 String s = st.nextToken().replace( ' ', '+' );
511 minx = Double.parseDouble( s );
512 s = st.nextToken().replace( ' ', '+' );
513 miny = Double.parseDouble( s );
514 s = st.nextToken().replace( ' ', '+' );
515 maxx = Double.parseDouble( s );
516 s = st.nextToken().replace( ' ', '+' );
517 maxy = Double.parseDouble( s );
518 }
519
520 // check for consistency
521 if ( minx >= maxx ) {
522 throw new InvalidFormatException( "minx must be lower than maxx" );
523 }
524
525 if ( miny >= maxy ) {
526 throw new InvalidFormatException( "miny must be lower than maxy" );
527 }
528
529 boundingBox = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null );
530
531 // TRANSPARENCY
532 boolean transparency = false;
533 String tp = model.remove( "TRANSPARENT" );
534 if ( tp != null ) {
535 transparency = tp.toUpperCase().trim().equals( "TRUE" );
536 }
537
538 String mime = MimeTypeMapper.toMimeType( format );
539 if ( mime.equals( "image/jpg" ) || mime.equals( "image/jpeg" ) || mime.equals( "image/bmp" )
540 || mime.equals( "image/tif" ) || mime.equals( "image/tiff" ) ) {
541 transparency = false;
542 }
543
544 // BGCOLOR
545 tmp = model.remove( "BGCOLOR" );
546 Color bgColor = Color.white;
547 if ( tmp != null ) {
548 bgColor = Color.decode( tmp );
549 }
550
551 // EXCEPTIONS
552 String exceptions = model.remove( "EXCEPTIONS" );
553
554 if ( exceptions == null ) {
555 if ( is130 ) {
556 exceptions = "XML";
557 } else {
558 exceptions = "application/vnd.ogc.se_xml";
559 }
560 }
561
562 // WFS
563 /*
564 * URL wFS_URL = null; if ((String)model.get( "WFS" ) != null) { wFS_URL = new URL((String)model.remove( "WFS"
565 * )); }
566 */
567
568 // ID
569 String id = model.remove( "ID" );
570 if ( id == null ) {
571 throw new InconsistentRequestException( "ID-value must be set" );
572 }
573
574 String dim = model.remove( "TIME" );
575 DimensionValues dimTime = dim == null ? null : new DimensionValues( dim );
576 dim = model.remove( "ELEVATION" );
577 DimensionValues dimElev = dim == null ? null : new DimensionValues( dim );
578
579 // VendorSpecificParameter; because all defined parameters has been
580 // removed
581 // from the model the vendorSpecificParameters are what left
582 Map<String, String> vendorSpecificParameter = new HashMap<String, String>();
583 for ( Object str : model.keySet() ) {
584 vendorSpecificParameter.put( str.toString(), model.get( str ).toString() );
585 }
586
587 return new GetMap( version, id, ls, format, width, height, srs, boundingBox, transparency, bgColor, exceptions,
588 sLD_URL, sld, vendorSpecificParameter, dimTime, dimElev );
589 }
590
591 /**
592 * creates a <tt>GetMapRequest</tt> from its XML representation as defined in SLD 1.0.0 specification
593 *
594 * <p>
595 * This method does not yet cope with 1.3.0.
596 * </p>
597 *
598 * @param id
599 * an unique id of the request
600 * @param doc
601 * the document tree
602 * @return an instance of <tt>GetMapRequest</tt>
603 * @throws XMLParsingException
604 * @throws InvalidSRSException
605 * @throws InconsistentRequestException
606 */
607 public static GetMap create( String id, Document doc )
608 throws XMLParsingException, InvalidSRSException, InconsistentRequestException {
609 String PSLD = CommonNamespaces.SLD_PREFIX + ':';
610
611 Element root = doc.getDocumentElement();
612 NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
613 Element sldElem = (Element) XMLTools.getRequiredNode( root, PSLD + "StyledLayerDescriptor", nsContext );
614 XMLFragment xml = new XMLFragment();
615 xml.setRootElement( sldElem );
616
617 StyledLayerDescriptor sld = SLDFactory.createSLD( xml );
618 String version = root.getAttribute( "version" );
619
620 boolean is130 = false;
621
622 if ( "1.3.0".compareTo( version ) <= 0 ) {
623 is130 = true;
624 }
625
626 Element bboxElem = (Element) XMLTools.getRequiredNode( root, PSLD + "BoundingBox", nsContext );
627
628 Envelope bbox;
629 try {
630 bbox = GMLGeometryAdapter.wrapBox( bboxElem, null );
631 // check for consistency
632 if ( bbox.getMin().getX() >= bbox.getMax().getX() ) {
633 throw new InvalidFormatException( "minx must be lower than maxx" );
634 }
635
636 if ( bbox.getMin().getY() >= bbox.getMax().getY() ) {
637 throw new InvalidFormatException( "miny must be lower than maxy" );
638 }
639 } catch ( InvalidGMLException e ) {
640 LOG.logDebug( e.getLocalizedMessage(), e );
641 if ( bboxElem == null ) {
642 throw new InconsistentRequestException( Messages.getMessage( "WMS_NO_BOUNDINGBOX" ) );
643 }
644 if ( is130 ) {
645 throw new InvalidCRSException( Messages.getMessage( "WMS_UNKNOWN_CRS",
646 bboxElem.getAttribute( "srsName" ) ) );
647 }
648 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", bboxElem.getAttribute( "srsName" ) ) );
649 } catch ( UnknownCRSException e ) {
650 LOG.logDebug( e.getLocalizedMessage(), e );
651 if ( bboxElem == null ) {
652 throw new InconsistentRequestException( Messages.getMessage( "WMS_NO_BOUNDINGBOX" ) );
653 }
654 if ( is130 ) {
655 throw new InvalidCRSException( Messages.getMessage( "WMS_UNKNOWN_CRS",
656 bboxElem.getAttribute( "srsName" ) ) );
657 }
658 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", bboxElem.getAttribute( "srsName" ) ) );
659 }
660
661 String srs = bbox.getCoordinateSystem().getIdentifier().toString();
662
663 Element output = (Element) XMLTools.getRequiredNode( root, PSLD + "Output", nsContext );
664
665 boolean transparent = XMLTools.getNodeAsBoolean( output, PSLD + "Transparent", nsContext, false );
666
667 int width = 0;
668 int height = 0;
669 try {
670 Element node = (Element) XMLTools.getRequiredNode( output, PSLD + "Size", nsContext );
671 width = XMLTools.getRequiredNodeAsInt( node, PSLD + "Width", nsContext );
672 height = XMLTools.getRequiredNodeAsInt( node, PSLD + "Height", nsContext );
673 } catch ( XMLParsingException e ) {
674 throw new InconsistentRequestException( Messages.getMessage( "WMS_REQUEST_SIZE" ) );
675 }
676
677 String exception = XMLTools.getNodeAsString( output, PSLD + "Exceptions", nsContext,
678 "application/vnd.ogc.se_xml" );
679 String sbgColor = XMLTools.getNodeAsString( output, PSLD + "BGColor", nsContext, "#FFFFFF" );
680 Color bgColor = Color.decode( sbgColor );
681
682 String format = XMLTools.getRequiredNodeAsString( output, PSLD + "Format", nsContext );
683 if ( format == null ) {
684 throw new InconsistentRequestException( "FORMAT-value must be set" );
685 }
686 try {
687 format = URLDecoder.decode( format, CharsetUtils.getSystemCharset() );
688 } catch ( UnsupportedEncodingException e1 ) {
689 e1.printStackTrace();
690 }
691 if ( !MimeTypeMapper.isKnownImageType( format ) ) {
692 throw new InvalidFormatException( format + " is not a valid image/result format" );
693 }
694
695 GetMap req = new GetMap( version, id, null, null, null, format, width, height, srs, bbox, transparent, bgColor,
696 exception, null, null, sld, null );
697
698 return req;
699 }
700
701 /**
702 * Creates a new GetMapRequest object.
703 *
704 * @param version
705 * @param id
706 * @param layers
707 * @param elevation
708 * @param sampleDimension
709 * @param format
710 * @param width
711 * @param height
712 * @param srs
713 * @param boundingBox
714 * @param transparency
715 * @param bGColor
716 * @param exceptions
717 * @param time
718 * @param sldURL
719 * @param sld
720 * @param vendorSpecificParameter
721 *
722 */
723 GetMap( String version, String id, Layer[] layers, Values elevation, Map<String, Values> sampleDimension,
724 String format, int width, int height, String srs, Envelope boundingBox, boolean transparency,
725 Color bGColor, String exceptions, Values time, URL sldURL, StyledLayerDescriptor sld,
726 Map<String, String> vendorSpecificParameter ) {
727 super( version, id, vendorSpecificParameter );
728
729 if ( layers != null ) {
730 this.layers = Arrays.asList( layers );
731 } else {
732 this.layers = new ArrayList<Layer>();
733 }
734 this.sld = sld;
735 this.elevation = elevation;
736 this.sampleDimension = sampleDimension;
737 this.format = format;
738 this.width = width;
739 this.height = height;
740 this.srs = srs;
741 this.boundingBox = boundingBox;
742 this.transparency = transparency;
743 this.bGColor = bGColor;
744 this.exceptions = exceptions;
745 this.time = time;
746 this.sLD_URL = sldURL;
747 // setWFS_URL( wFS_URL );
748 }
749
750 /**
751 * @param version
752 * @param id
753 * @param ls
754 * @param format
755 * @param width
756 * @param height
757 * @param srs
758 * @param boundingBox
759 * @param transparency
760 * @param bgColor
761 * @param exceptions
762 * @param sld_url
763 * @param sld
764 * @param vendorSpecificParameter
765 * @param dimTime
766 * @param dimElev
767 */
768 public GetMap( String version, String id, Layer[] ls, String format, int width, int height, String srs,
769 Envelope boundingBox, boolean transparency, Color bgColor, String exceptions, URL sld_url,
770 StyledLayerDescriptor sld, Map<String, String> vendorSpecificParameter, DimensionValues dimTime,
771 DimensionValues dimElev ) {
772 this( version, id, ls, null, null, format, width, height, srs, boundingBox, transparency, bgColor, exceptions,
773 null, sld_url, sld, vendorSpecificParameter );
774 this.dimTime = dimTime;
775 this.dimElev = dimElev;
776 }
777
778 /**
779 * The FORMAT parameter specifies the output format of the response to an operation.
780 * <p>
781 * </p>
782 * An OGC Web CapabilitiesService may offer only a subset of the formats known for that type of operation, but the
783 * server shall advertise in its Capabilities XML those formats it does support and shall accept requests for any
784 * format it advertises. A CapabilitiesService Instance may optionally offer a new format not previously offered by
785 * other instances, with the recognition that clients are not required to accept or process an unknown format. If a
786 * request contains a Format not offered by a particular server, the server shall throw a CapabilitiesService
787 * Exception (with code "InvalidFormat").
788 *
789 * @return the output format
790 */
791 public String getFormat() {
792 return format;
793 }
794
795 /**
796 * sets the format
797 *
798 * @param format
799 * the requested output-format
800 */
801 public void setFormat( String format ) {
802 this.format = format;
803 }
804
805 /**
806 * The required LAYERS parameter lists the map layer(s) to be returned by this GetMap request. The value of the
807 * LAYERS parameter is a comma-separated list of one or more valid layer names. Allowed layer names are the
808 * character data content of any <Layer><Name> element in the Capabilities XML.
809 * <p>
810 * </p>
811 * A WMS shall render the requested layers by drawing the leftmost in the list bottommost, the next one over that,
812 * and so on.
813 * <p>
814 * </p>
815 * Each layer is associated to a style. Styles are also is encoded as a comma- seperated list within the GetMap
816 * request.
817 * <p>
818 * </p>
819 * The required STYLES parameter lists the style in which each layer is to be rendered. There is a one-to-one
820 * correspondence between the values in the LAYERS parameter and the values in the STYLES parameter. Because of this
821 * layer-style combinations are returned coupled within an array of Layer- objects. Each map in the list of LAYERS
822 * is drawn using the corresponding style in the same position in the list of STYLES. Each style Name shall be one
823 * that was defined in the <Name> element of a <Style> element that is either directly contained within, or
824 * inherited by, the associated <Layer> element in Capabilities XML.
825 *
826 * @return The required LAYERS
827 */
828 public Layer[] getLayers() {
829 return layers.toArray( new Layer[layers.size()] );
830 }
831
832 /**
833 * adds the <Layer>
834 *
835 * @param layers
836 */
837 public void addLayers( Layer layers ) {
838 this.layers.add( layers );
839 }
840
841 /**
842 * sets the <Layer>
843 *
844 * @param layers
845 * a set of layer
846 */
847 public void setLayers( Layer[] layers ) {
848 this.layers.clear();
849
850 if ( layers != null ) {
851 for ( int i = 0; i < layers.length; i++ ) {
852 this.layers.add( layers[i] );
853 }
854 }
855 }
856
857 /**
858 * The required SRS parameter states which Spatial Reference System applies to the values in the BBOX parameter. The
859 * value of the SRS parameter shall be one of the values defined in the character data section of an <SRS> element
860 * defined or inherited by the requested layer. The same SRS applies to all layers in a single request.
861 * <p>
862 * </p>
863 * If the WMS server has declared SRS=NONE for a Layer, as discussed in the Basic CapabilitiesService Elements
864 * section, then the Layer does not have a well-defined spatial reference system and should not be shown in
865 * conjunction with other layers. The Client shall specify SRS=NONE (case-insensitive) in the GetMap request and the
866 * Server may issue a CapabilitiesService Exception otherwise.
867 *
868 * @return the spatial reference system
869 */
870 public String getSrs() {
871 return srs;
872 }
873
874 /**
875 * sets the srs
876 *
877 * @param srs
878 * the spatial reference system
879 */
880 public void setSrs( String srs ) {
881 this.srs = srs;
882 }
883
884 /**
885 * The required BBOX parameter allows a Client to request a particular Bounding Box. Bounding Boxes are defined in
886 * the Basic CapabilitiesService Elements section. The value of the BBOX parameter in a GetMap request is a list of
887 * comma-separated numbers of the form "minx,miny,maxx,maxy".
888 * <p>
889 * </p>
890 * If the WMS server has declared that a Layer is not subsettable then the Client shall specify exactly the declared
891 * Bounding Box values in the GetMap request and the Server may issue a CapabilitiesService Exception otherwise.
892 *
893 * @return the bounding box
894 */
895 public Envelope getBoundingBox() {
896 return boundingBox;
897 }
898
899 /**
900 * @see #getBoundingBox()
901 * @param boundingBox
902 */
903 public void setBoundingBox( Envelope boundingBox ) {
904 this.boundingBox = boundingBox;
905 }
906
907 /**
908 * WIDTH specifies the number of pixels to be used between the minimum and maximum X values (inclusive) in the BBOX
909 * parameter. The returned picture, regardless of its return format, shall have exactly the specified width and
910 * height in pixels. In the case where the aspect ratio of the BBOX and the ratio width/height are different, the
911 * WMS shall stretch the returned map so that the resulting pixels could themselves be rendered in the aspect ratio
912 * of the BBOX. In other words, it should be possible using this definition to request a map for a device whose
913 * output pixels are themselves non-square, or to stretch a map into an image area of a different aspect ratio.
914 *
915 * @return the width
916 */
917 public int getWidth() {
918 return width;
919 }
920
921 /**
922 * @see #getWidth()
923 * @param width
924 */
925 public void setWidth( int width ) {
926 this.width = width;
927 }
928
929 /**
930 * HEIGHT specifies the number of pixels between the minimum and maximum Y values. The returned picture, regardless
931 * of its return format, shall have exactly the specified width and height in pixels. In the case where the aspect
932 * ratio of the BBOX and the ratio width/height are different, the WMS shall stretch the returned map so that the
933 * resulting pixels could themselves be rendered in the aspect ratio of the BBOX. In other words, it should be
934 * possible using this definition to request a map for a device whose output pixels are themselves non-square, or to
935 * stretch a map into an image area of a different aspect ratio.
936 *
937 * @return the height
938 */
939 public int getHeight() {
940 return height;
941 }
942
943 /**
944 * @see #getHeight()
945 * @param height
946 */
947 public void setHeight( int height ) {
948 this.height = height;
949 }
950
951 /**
952 * The optional TRANSPARENT parameter specifies whether the map background is to be made transparent or not.
953 * TRANSPARENT can take on two values, "TRUE" or "FALSE". The default value is FALSE if this parameter is absent
954 * from the request.
955 * <p>
956 * </p>
957 * The ability to return pictures drawn with transparent pixels allows results of different Map requests to be
958 * overlaid, producing a composite map. It is strongly recommended that every WMS offer a format that provides
959 * transparency for layers which could sensibly be overlaid above others.
960 *
961 * @return the transparency setting
962 */
963 public boolean getTransparency() {
964 return transparency;
965 }
966
967 /**
968 * The optional BGCOLOR parameter specifies the color to be used as the background of the map. The general format of
969 * BGCOLOR is a hexadecimal encoding of an RGB value where two hexadecimal characters are used for each of Red,
970 * Green, and Blue color values. The values can range between 00 and FF for each (0 and 255, base 10). The format is
971 * 0xRRGGBB; either upper or lower case characters are allowed for RR, GG, and BB values. The "0x" prefix shall have
972 * a lower case 'x'. The default value is 0xFFFFFF (corresponding to the color white) if this parameter is absent
973 * from the request.
974 *
975 * @return the background color
976 */
977 public Color getBGColor() {
978 return bGColor;
979 }
980
981 /**
982 * The optional EXCEPTIONS parameter states the manner in which errors are to be reported to the client. The default
983 * value is application/vnd.ogc.se_xml if this parameter is absent from the request.
984 * <p>
985 * </p>
986 * A Web Map CapabilitiesService shall offer one or more of the following exception reporting formats by listing
987 * them in separate <Format> elements inside the <Exceptions> element of its Capabilities XML. The entire MIME type
988 * string in <Format> is used as the value of the EXCEPTIONS parameter. The first of these formats is required to be
989 * offered by every WMS; the others are optional.
990 *
991 * @return the exceptions parameter
992 */
993 public String getExceptions() {
994 return exceptions;
995 }
996
997 /**
998 * This specification is based on [ISO 8601:1988(E)]; it extends ISO 8601 in the following ways:
999 * <UL>
1000 * <li>It defines a syntax for expressing the start, end and periodicity of a data collection.
1001 * <li>It defines terms to represent the 7 days of the week.
1002 * <li>It allows years before 0001 AD.
1003 * <li>It allows times in the distant geologic past (thousands, millions or billions of years before present).
1004 * </UL>
1005 *
1006 * @return the time setting
1007 */
1008 public Values getTime() {
1009 return time;
1010 }
1011
1012 /**
1013 * Some geospatial information may be available at multiple elevations. An OWS may announce available elevations in
1014 * its Capabilities XML, and some operations include a parameter for requesting a particular elevation. A single
1015 * elevation value is an integer or real number whose units are declared by naming an EPSG datum. When providing
1016 * elevation information, Servers should declare a default value in Capabilities XML unless there is compelling
1017 * reason to behave otherwise, and Servers shall respond with the default value if one has been declared and the
1018 * Client request does not include a value.
1019 *
1020 * @return the elevation
1021 */
1022 public Values getElevation() {
1023 return elevation;
1024 }
1025
1026 /**
1027 * Some geospatial information may be available at other dimensions (for example, satellite images in different
1028 * wavelength bands). The dimensions other than the four space-time dimensions are referred to as
1029 * "sample dimensions". An OWS may announce available sample dimensions in its Capabilities XML, and some operations
1030 * include a mechanism for including dimensional parameters. Each sample dimension has a Name and one or more valid
1031 * values.
1032 *
1033 * @return the map
1034 */
1035 public Map<String, Values> getSampleDimension() {
1036 return sampleDimension;
1037 }
1038
1039 /**
1040 * @return the URL of Styled Layer Descriptor (as defined in SLD Specification). This parameter is optional. If no
1041 * sld URL is defined <tt>null</tt> will be returned.
1042 */
1043 public URL getSLD_URL() {
1044 return sLD_URL;
1045 }
1046
1047 /**
1048 * @return the URL of Web Feature CapabilitiesService providing features to be symbolized using SLD. This parameter
1049 * is optional. If no WFS URL is defined <tt>null</tt> will be returned.
1050 */
1051 public URL getWFS_URL() {
1052 return wFS_URL;
1053 }
1054
1055 /**
1056 * @return the SLD the request is made of. This implies that a 'simple' HTTP GET-Request will be transformed into a
1057 * valid SLD. This is mandatory within a JaGo WMS.
1058 * <p>
1059 * </p>
1060 * This mean even if a GetMap request is send using the HTTP GET method, an implementing class has to map
1061 * the request to a SLD data structure.
1062 */
1063 public StyledLayerDescriptor getStyledLayerDescriptor() {
1064 return sld;
1065 }
1066
1067 /**
1068 * @return the parameter of a HTTP GET request.
1069 *
1070 */
1071 @Override
1072 public String getRequestParameter()
1073 throws OGCWebServiceException {
1074
1075 // indicates if the request parameters are decoded as SLD. deegree won't
1076 // perform SLD requests through HTTP GET
1077 if ( boundingBox == null ) {
1078 throw new OGCWebServiceException( "Operations can't be expressed as HTTP GET request " );
1079 }
1080
1081 StringBuffer sb = new StringBuffer();
1082
1083 if ( getVersion().compareTo( "1.0.0" ) <= 0 ) {
1084 sb.append( "VERSION=" ).append( getVersion() ).append( "&REQUEST=map" );
1085 String f = StringTools.replace( getFormat(), "image/", "", false );
1086 sb.append( "&FORMAT=" );
1087 try {
1088 sb.append( URLEncoder.encode( f, CharsetUtils.getSystemCharset() ) );
1089 } catch ( UnsupportedEncodingException e ) {
1090 // system encoding should be supported...
1091 }
1092 } else {
1093 sb.append( "&VERSION=" ).append( getVersion() ).append( "&REQUEST=GetMap" );
1094 sb.append( "&FORMAT=" );
1095 try {
1096 sb.append( URLEncoder.encode( getFormat(), CharsetUtils.getSystemCharset() ) );
1097 } catch ( UnsupportedEncodingException e ) {
1098 // system encoding should be supported...
1099 }
1100 }
1101
1102 sb.append( "&TRANSPARENT=" ).append( Boolean.toString( getTransparency() ).toUpperCase() );
1103 sb.append( "&WIDTH=" ).append( getWidth() );
1104 sb.append( "&HEIGHT=" ).append( getHeight() );
1105 sb.append( "&EXCEPTIONS=" ).append( getExceptions() );
1106 sb.append( "&BGCOLOR=" ).append( ColorUtils.toHexCode( "0x", bGColor ) );
1107
1108 if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
1109 sb.append( "&BBOX=" ).append( boundingBox.getMin().getY() );
1110 sb.append( ',' ).append( boundingBox.getMin().getX() );
1111 sb.append( ',' ).append( boundingBox.getMax().getY() );
1112 sb.append( ',' ).append( boundingBox.getMax().getX() );
1113 } else {
1114 sb.append( "&BBOX=" ).append( boundingBox.getMin().getX() );
1115 sb.append( ',' ).append( boundingBox.getMin().getY() );
1116 sb.append( ',' ).append( boundingBox.getMax().getX() );
1117 sb.append( ',' ).append( boundingBox.getMax().getY() );
1118 }
1119
1120 Layer[] layers = getLayers();
1121 StringBuffer l = new StringBuffer( 500 );
1122 StringBuffer s = new StringBuffer( 500 );
1123
1124 if ( sLD_URL == null ) {
1125 for ( int i = 0; i < layers.length; i++ ) {
1126 try {
1127 l.append( URLEncoder.encode( layers[i].getName(), CharsetUtils.getSystemCharset() ) );
1128 l.append( ',' );
1129 if ( !layers[i].getStyleName().equals( "$DEFAULT" ) ) {
1130 s.append( URLEncoder.encode( layers[i].getStyleName(), CharsetUtils.getSystemCharset() ) );
1131 }
1132 s.append( ',' );
1133 } catch ( Exception e ) {
1134 throw new OGCWebServiceException( e.toString() );
1135 }
1136 }
1137
1138 if ( l.length() != 0 ) {
1139 sb.append( "&LAYERS=" ).append( l.substring( 0, l.length() - 1 ) );
1140 }
1141
1142 if ( s.length() != 0 ) {
1143 sb.append( "&STYLES=" ).append( s.substring( 0, s.length() - 1 ) );
1144 }
1145 } else if ( sLD_URL != null ) {
1146 sb.append( "&SLD=" ).append( NetWorker.url2String( sLD_URL ) );
1147 } else if ( sld != null ) {
1148 String tmp = ( (Marshallable) sld ).exportAsXML();
1149 try {
1150 tmp = URLEncoder.encode( tmp, CharsetUtils.getSystemCharset() );
1151 } catch ( Exception e ) {
1152 throw new OGCWebServiceException( e.toString() );
1153 }
1154 sb.append( "&SLD_BODY=" ).append( tmp );
1155 }
1156
1157 if ( "1.3.0".compareTo( getVersion() ) <= 0 ) {
1158 sb.append( "&CRS=" ).append( getSrs() );
1159 } else {
1160 sb.append( "&SRS=" ).append( getSrs() );
1161 }
1162
1163 // TODO
1164 // append time, elevation and sampleDimensions
1165
1166 if ( getVendorSpecificParameters() != null ) {
1167 Iterator<String> iterator = getVendorSpecificParameters().keySet().iterator();
1168 while ( iterator.hasNext() ) {
1169 String key = iterator.next();
1170 String value = getVendorSpecificParameters().get( key );
1171 try {
1172 value = URLEncoder.encode( value, CharsetUtils.getSystemCharset() );
1173 } catch ( UnsupportedEncodingException e ) {
1174 // system encoding should be supported...
1175 }
1176 sb.append( '&' ).append( key ).append( '=' ).append( value );
1177 }
1178 }
1179
1180 return sb.toString();
1181 }
1182
1183 @Override
1184 public String toString() {
1185 String s = "An unknown " + this.getClass().getName() + " request";
1186 try {
1187 s = getRequestParameter();
1188 } catch ( OGCWebServiceException e ) {
1189 // in that case, we just don't have the parameters...
1190 }
1191 return s;
1192 }
1193
1194 /**
1195 * creates a Layer object beacuse of the inner class construct.
1196 *
1197 * @param name
1198 * the name of the layer
1199 * @param style
1200 * the corresponding style of the layer
1201 * @return Layer a layer object constaining name and style
1202 */
1203 public static Layer createLayer( String name, String style ) {
1204 return new Layer( name, style );
1205 }
1206
1207 /**
1208 * A Layer object. It contains the name of the layer and the corresponding style.
1209 *
1210 * @version $Revision: 21363 $
1211 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1212 */
1213 public static class Layer implements Serializable {
1214
1215 private static final long serialVersionUID = -98575941104285931L;
1216
1217 private String name = null;
1218
1219 private String styleName = null;
1220
1221 /**
1222 * constructor initializing the class with the <Layer>
1223 *
1224 * @param name
1225 * @param styleName
1226 */
1227 public Layer( String name, String styleName ) {
1228 this.name = name;
1229 this.styleName = styleName;
1230 }
1231
1232 /**
1233 * @return the <Name>
1234 */
1235 public String getName() {
1236 return name;
1237 }
1238
1239 /**
1240 * @return the <StyleName>
1241 */
1242 public String getStyleName() {
1243 return styleName;
1244 }
1245
1246 }
1247
1248 /**
1249 * @return the dimTime
1250 */
1251 public DimensionValues getDimTime() {
1252 return dimTime;
1253 }
1254
1255 /**
1256 * @param dimTime
1257 * the dimTime to set
1258 */
1259 public void setDimTime( DimensionValues dimTime ) {
1260 this.dimTime = dimTime;
1261 }
1262
1263 /**
1264 * @return the dimElev
1265 */
1266 public DimensionValues getDimElev() {
1267 return dimElev;
1268 }
1269
1270 /**
1271 * @param dimElev
1272 * the dimElev to set
1273 */
1274 public void setDimElev( DimensionValues dimElev ) {
1275 this.dimElev = dimElev;
1276 }
1277
1278 }