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 }