001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wmps/DefaultGetMapHandler.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2007 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 53115 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 package org.deegree.ogcwebservices.wmps; 044 045 import java.awt.Color; 046 import java.awt.Font; 047 import java.awt.Graphics; 048 import java.awt.Graphics2D; 049 import java.awt.RenderingHints; 050 import java.awt.image.BufferedImage; 051 import java.util.ArrayList; 052 import java.util.List; 053 054 import org.deegree.framework.log.ILogger; 055 import org.deegree.framework.log.LoggerFactory; 056 import org.deegree.framework.util.CharsetUtils; 057 import org.deegree.framework.util.ImageUtils; 058 import org.deegree.framework.util.MapUtils; 059 import org.deegree.framework.util.StringTools; 060 import org.deegree.framework.xml.XMLParsingException; 061 import org.deegree.graphics.MapFactory; 062 import org.deegree.graphics.MapView; 063 import org.deegree.graphics.Theme; 064 import org.deegree.graphics.optimizers.LabelOptimizer; 065 import org.deegree.graphics.sld.AbstractLayer; 066 import org.deegree.graphics.sld.AbstractStyle; 067 import org.deegree.graphics.sld.NamedLayer; 068 import org.deegree.graphics.sld.NamedStyle; 069 import org.deegree.graphics.sld.SLDFactory; 070 import org.deegree.graphics.sld.StyledLayerDescriptor; 071 import org.deegree.graphics.sld.UserLayer; 072 import org.deegree.graphics.sld.UserStyle; 073 import org.deegree.i18n.Messages; 074 import org.deegree.model.crs.CRSFactory; 075 import org.deegree.model.crs.CoordinateSystem; 076 import org.deegree.model.crs.GeoTransformer; 077 import org.deegree.model.crs.IGeoTransformer; 078 import org.deegree.model.spatialschema.Envelope; 079 import org.deegree.model.spatialschema.Geometry; 080 import org.deegree.model.spatialschema.GeometryFactory; 081 import org.deegree.ogcbase.InvalidSRSException; 082 import org.deegree.ogcwebservices.OGCWebServiceException; 083 import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration; 084 import org.deegree.ogcwebservices.wmps.configuration.WMPSDeegreeParams; 085 import org.deegree.ogcwebservices.wms.LayerNotDefinedException; 086 import org.deegree.ogcwebservices.wms.StyleNotDefinedException; 087 import org.deegree.ogcwebservices.wms.capabilities.Layer; 088 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint; 089 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 090 import org.deegree.ogcwebservices.wms.operation.GetMap; 091 092 /** 093 * This is a copy of the WMS package. 094 * 095 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 096 * @author last edited by: $Author: apoth $ 097 * 098 * @version $Revision: 7177 $, $Date: 2007-05-16 16:20:27 +0200 (Mi, 16 Mai 2007) $ 099 */ 100 public class DefaultGetMapHandler implements GetMapHandler { 101 102 private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class ); 103 104 protected GetMap request; 105 106 private Object[] themes; 107 108 protected double scale = 0; 109 110 private int count = 0; 111 112 protected CoordinateSystem reqCRS; 113 114 private WMPSConfiguration configuration; 115 116 private BufferedImage copyrightImg; 117 118 private Graphics graph; 119 120 /** 121 * Creates a new GetMapHandler object. 122 * 123 * @param configuration 124 * @param request 125 * request to perform 126 * @throws OGCWebServiceException 127 */ 128 public DefaultGetMapHandler( WMPSConfiguration configuration, GetMap request ) throws OGCWebServiceException { 129 this.request = request; 130 this.configuration = configuration; 131 132 try { 133 // get copyright image if possible 134 this.copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyright() ); 135 } catch ( Exception e ) { 136 } 137 138 try { 139 this.reqCRS = CRSFactory.create( this.request.getSrs() ); 140 } catch ( Exception e ) { 141 throw new InvalidSRSException( "SRS: " + request.getSrs() + "is nor known by the deegree WMS" ); 142 } 143 144 } 145 146 /** 147 * returns the configuration used by the handler 148 * 149 * @return WMPSConfiguration 150 */ 151 public WMPSConfiguration getConfiguration() { 152 return this.configuration; 153 } 154 155 /** 156 * increases the counter variable that holds the number of services that has sent a response. 157 * All data are available if the counter value equals the number of requested layers. 158 */ 159 protected synchronized void increaseCounter() { 160 this.count++; 161 } 162 163 /** 164 * performs a GetMap request and retruns the result encapsulated within a <tt>GetMapResult</tt> 165 * object. 166 * <p> 167 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs 168 * that makes it imposible to return a result. If something wents wrong performing the request 169 * (none fatal error) The exception shall be encapsulated within the response object to be 170 * returned to the client as requested (GetMap-Request EXCEPTION-Parameter). 171 * 172 * @param g 173 * @throws OGCWebServiceException 174 */ 175 public void performGetMap( Graphics g ) 176 throws OGCWebServiceException { 177 178 this.graph = g; 179 180 try { 181 CoordinateSystem crs = CRSFactory.create( this.request.getSrs() ); 182 this.scale = MapUtils.calcScale( this.request.getWidth(), this.request.getHeight(), 183 this.request.getBoundingBox(), crs, 1 ); 184 LOG.logInfo( "OGC WMS scale: " + this.scale ); 185 } catch ( Exception e ) { 186 LOG.logDebug( "-", e ); 187 throw new OGCWebServiceException( "Couldn't calculate scale! " + e ); 188 } 189 190 StyledLayerDescriptor sld = null; 191 try { 192 sld = toSLD( this.request.getLayers(), this.request.getStyledLayerDescriptor() ); 193 } catch ( XMLParsingException e1 ) { 194 // should never happen 195 e1.printStackTrace(); 196 } 197 198 AbstractLayer[] layers = sld.getLayers(); 199 // get the number of themes assigned to the selected layers 200 // notice that there maybe more themes as there are layers because 201 // 1 .. n datasources can be assigned to one layer. 202 int cntTh = countNumberOfThemes( layers, this.scale ); 203 this.themes = new Object[cntTh]; 204 // invokes the data supplyer for each layer in an independent thread 205 int kk = 0; 206 for ( int i = 0; i < layers.length; i++ ) { 207 if ( layers[i] instanceof NamedLayer ) { 208 String styleName = null; 209 if ( i < this.request.getLayers().length ) { 210 styleName = this.request.getLayers()[i].getStyleName(); 211 } 212 kk = invokeNamedLayer( layers[i], kk, styleName ); 213 } else { 214 GetMapServiceInvokerForUL si = new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], kk++ ); 215 216 si.start(); 217 } 218 } 219 waitForFinished(); 220 renderMap(); 221 222 } 223 224 /** 225 * Invoke the named layer 226 * 227 * @param layer 228 * @param kk 229 * @param styleName 230 * @return int 231 * @throws OGCWebServiceException 232 */ 233 private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName ) 234 throws OGCWebServiceException { 235 236 Layer lay = this.configuration.getLayer( layer.getName() ); 237 238 if ( validate( lay, layer.getName() ) ) { 239 UserStyle us = getStyles( (NamedLayer) layer, styleName ); 240 AbstractDataSource[] ds = lay.getDataSource(); 241 242 for ( int j = 0; j < ds.length; j++ ) { 243 244 ScaleHint scaleHint = ds[j].getScaleHint(); 245 if ( this.scale >= scaleHint.getMin() && this.scale < scaleHint.getMax() 246 && isValidArea( ds[j].getValidArea() ) ) { 247 GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, lay, ds[j], us, kk++ ); 248 si.start(); 249 } 250 } 251 } else { 252 // set theme to null if no data are available for the requested 253 // area and/or scale 254 this.themes[kk++] = null; 255 increaseCounter(); 256 } 257 return kk; 258 } 259 260 /** 261 * returns the number of <code>DataSource</code>s involved in a GetMap request 262 * 263 * @param layers 264 * @param currentscale 265 * @return int 266 */ 267 private int countNumberOfThemes( AbstractLayer[] layers, double currentscale ) { 268 int cnt = 0; 269 for ( int i = 0; i < layers.length; i++ ) { 270 if ( layers[i] instanceof NamedLayer ) { 271 Layer lay = this.configuration.getLayer( layers[i].getName() ); 272 AbstractDataSource[] ds = lay.getDataSource(); 273 for ( int j = 0; j < ds.length; j++ ) { 274 275 ScaleHint scaleHint = ds[j].getScaleHint(); 276 if ( currentscale >= scaleHint.getMin() && currentscale < scaleHint.getMax() 277 && isValidArea( ds[j].getValidArea() ) ) { 278 279 cnt++; 280 } 281 } 282 } else { 283 cnt++; 284 } 285 } 286 return cnt; 287 } 288 289 /** 290 * returns true if the requested boundingbox intersects with the valid area of a datasource 291 * 292 * @param validArea 293 * @return boolean 294 */ 295 private boolean isValidArea( Geometry validArea ) { 296 297 if ( validArea != null ) { 298 try { 299 Envelope env = this.request.getBoundingBox(); 300 Geometry geom = GeometryFactory.createSurface( env, this.reqCRS ); 301 if ( !this.reqCRS.getName().equals( validArea.getCoordinateSystem().getName() ) ) { 302 // if requested CRS is not identical to the CRS of the valid area 303 // a transformation must be performed before intersection can 304 // be checked 305 IGeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() ); 306 geom = gt.transform( geom ); 307 } 308 return geom.intersects( validArea ); 309 } catch ( Exception e ) { 310 // should never happen 311 LOG.logError( "could not validate WMS datasource area", e ); 312 } 313 } 314 return true; 315 } 316 317 /** 318 * runs a loop until all sub requestes (one for each layer) has been finished or the maximum 319 * time limit has been exceeded. 320 * 321 * @throws OGCWebServiceException 322 */ 323 private void waitForFinished() 324 throws OGCWebServiceException { 325 if ( this.count < this.themes.length ) { 326 // waits until the requested layers are available as <tt>DisplayElements</tt> 327 // or the time limit has been reached. 328 // if count == themes.length then no request must be performed 329 long timeStamp = System.currentTimeMillis(); 330 long lapse = 0; 331 long timeout = 1000 * ( this.configuration.getDeegreeParams().getRequestTimeLimit() - 1 ); 332 do { 333 try { 334 Thread.sleep( 50 ); 335 lapse += 50; 336 } catch ( InterruptedException e ) { 337 throw new OGCWebServiceException( "GetMapHandler", "fatal exception waiting for " 338 + "GetMapHandler results" ); 339 } 340 } while ( this.count < this.themes.length && lapse < timeout ); 341 if ( System.currentTimeMillis() - timeStamp >= timeout ) { 342 throw new OGCWebServiceException( "Processing of the GetMap request " + "exceeds timelimit" ); 343 } 344 } 345 } 346 347 /** 348 * 349 * @param layers 350 * @param inSLD 351 * @return StyledLayerDescriptor 352 * @throws XMLParsingException 353 */ 354 private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD ) 355 throws XMLParsingException { 356 StyledLayerDescriptor sld = null; 357 358 if ( layers != null && layers.length > 0 && inSLD == null ) { 359 // Adds the content from the LAYERS and STYLES attribute to the SLD 360 StringBuffer sb = new StringBuffer( 5000 ); 361 sb.append( "<?xml version=\"1.0\" encoding=\"" + CharsetUtils.getSystemCharset() + "\"?>" ); 362 sb.append( "<StyledLayerDescriptor version=\"1.0.0\" " ); 363 sb.append( "xmlns='http://www.opengis.net/sld'>" ); 364 365 for ( int i = 0; i < layers.length; i++ ) { 366 sb.append( "<NamedLayer>" ); 367 sb.append( "<Name>" + layers[i].getName() + "</Name>" ); 368 sb.append( "<NamedStyle><Name>" + layers[i].getStyleName() + "</Name></NamedStyle></NamedLayer>" ); 369 } 370 sb.append( "</StyledLayerDescriptor>" ); 371 372 try { 373 sld = SLDFactory.createSLD( sb.toString() ); 374 } catch ( XMLParsingException e ) { 375 throw new XMLParsingException( StringTools.stackTraceToString( e ) ); 376 } 377 } else if ( layers != null && layers.length > 0 && inSLD != null ) { 378 // if layers not null and sld is not null then SLD layers just be 379 // considered if present in the layers list 380 List<String> list = new ArrayList<String>(); 381 for ( int i = 0; i < layers.length; i++ ) { 382 list.add( layers[i].getName() ); 383 } 384 385 List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 ); 386 AbstractLayer[] al = inSLD.getLayers(); 387 for ( int i = 0; i < al.length; i++ ) { 388 if ( list.contains( al[i].getName() ) ) { 389 newList.add( al[i] ); 390 } 391 } 392 al = new AbstractLayer[newList.size()]; 393 sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() ); 394 } else { 395 // if no layers are defined ... 396 sld = inSLD; 397 } 398 399 return sld; 400 } 401 402 /** 403 * returns the <tt>UserStyle</tt>s assigned to a named layer 404 * 405 * @param sldLayer 406 * layer to get the styles for 407 * @param styleName 408 * requested stylename (from the KVP encoding) 409 * @return UserStyle 410 * @throws OGCWebServiceException 411 */ 412 private UserStyle getStyles( NamedLayer sldLayer, String styleName ) 413 throws OGCWebServiceException { 414 415 AbstractStyle[] styles = sldLayer.getStyles(); 416 UserStyle us = null; 417 418 // to avoid retrieving the layer again for each style 419 Layer layer = null; 420 layer = this.configuration.getLayer( sldLayer.getName() ); 421 int i = 0; 422 while ( us == null && i < styles.length ) { 423 if ( styles[i] instanceof NamedStyle ) { 424 // styles will be taken from the WMS's style repository 425 us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer ); 426 } else { 427 // if the requested style fits the name of the defined style or 428 // if the defined style is marked as default and the requested 429 // style if 'default' the condition is true. This includes that 430 // if more than one style with the same name or more than one 431 // style is marked as default always the first will be choosen 432 if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) ) 433 || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) { 434 us = (UserStyle) styles[i]; 435 } 436 } 437 i++; 438 } 439 if ( us == null ) { 440 // this may happens if the SLD contains a named layer but not 441 // a style! yes this is valid according to SLD spec 1.0.0 442 us = getPredefinedStyle( styleName, sldLayer.getName(), layer ); 443 } 444 return us; 445 } 446 447 /** 448 * Returns a Predifined UserStyle 449 * 450 * @param styleName 451 * @param layerName 452 * @param layer 453 * @return UserStyle 454 * @throws StyleNotDefinedException 455 */ 456 private UserStyle getPredefinedStyle( String styleName, String layerName, Layer layer ) 457 throws StyleNotDefinedException { 458 UserStyle us = null; 459 460 if ( "default".equals( styleName ) ) { 461 us = layer.getStyle( styleName ); 462 } 463 464 if ( us == null ) { 465 if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" ) 466 || styleName.equals( "default" ) ) { 467 styleName = "default:" + layerName; 468 } 469 } 470 471 us = layer.getStyle( styleName ); 472 if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) { 473 String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer ); 474 throw new StyleNotDefinedException( s ); 475 } 476 return us; 477 } 478 479 /** 480 * validates if the requested layer matches the conditions of the request if not a 481 * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't 482 * able to deviever data for the requested area and/or scale false will be returned. If the 483 * layer matches the request and contains data for the requested area and/or scale true will be 484 * returned. 485 * 486 * @param layer 487 * layer as defined at the capabilities/configuration 488 * @param name 489 * name of the layer (must be submitted seperatly because the layer parameter can be 490 * <tt>null</tt> 491 * @return boolean 492 * @throws OGCWebServiceException 493 */ 494 private boolean validate( Layer layer, String name ) 495 throws OGCWebServiceException { 496 497 // check if layer is available 498 if ( layer == null ) { 499 throw new LayerNotDefinedException( "Layer: " + name + " is not known by the WMS" ); 500 } 501 502 if ( !layer.isSrsSupported( this.request.getSrs() ) ) { 503 throw new InvalidSRSException( "SRS: " + this.request.getSrs() + "is not known by layer: " + name ); 504 } 505 506 // check for valid coordinated reference system 507 String[] srs = layer.getSrs(); 508 boolean tmp = false; 509 for ( int i = 0; i < srs.length; i++ ) { 510 if ( srs[i].equalsIgnoreCase( this.request.getSrs() ) ) { 511 tmp = true; 512 break; 513 } 514 } 515 516 if ( !tmp ) { 517 throw new InvalidSRSException( "layer: " + name + " can't be " + "delievered in SRS: " 518 + this.request.getSrs() ); 519 } 520 521 // check bounding box 522 try { 523 524 Envelope bbox = this.request.getBoundingBox(); 525 Envelope layerBbox = layer.getLatLonBoundingBox(); 526 if ( !this.request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) { 527 // transform the bounding box of the request to EPSG:4326 528 IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) ); 529 bbox = gt.transform( bbox, this.reqCRS ); 530 } 531 if ( !bbox.intersects( layerBbox ) ) { 532 return false; 533 } 534 535 } catch ( Exception e ) { 536 LOG.logError( e.getMessage(), e ); 537 throw new OGCWebServiceException( "couldn't compare bounding boxes\n" + e.toString() ); 538 } 539 540 return true; 541 } 542 543 /** 544 * put a theme to the passed index of the themes array. The second param passed is a 545 * <tt>Theme</tt> or an exception 546 * 547 * @param index 548 * @param o 549 */ 550 protected synchronized void putTheme( int index, Object o ) { 551 this.themes[index] = o; 552 } 553 554 /** 555 * renders the map from the <tt>DisplayElement</tt>s 556 */ 557 private void renderMap() { 558 559 // GetMapResult response = null; 560 OGCWebServiceException exce = null; 561 562 ArrayList<Object> list = new ArrayList<Object>( 50 ); 563 for ( int i = 0; i < this.themes.length; i++ ) { 564 if ( this.themes[i] instanceof Exception ) { 565 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", this.themes[i].toString() ); 566 } 567 if ( this.themes[i] instanceof OGCWebServiceException ) { 568 exce = (OGCWebServiceException) this.themes[i]; 569 break; 570 } 571 if ( this.themes[i] != null ) { 572 list.add( this.themes[i] ); 573 } 574 } 575 576 if ( exce == null ) { 577 // only if no exception occured 578 try { 579 Theme[] th = list.toArray( new Theme[list.size()] ); 580 MapView map = null; 581 if ( th.length > 0 ) { 582 map = MapFactory.createMapView( "deegree WMS", this.request.getBoundingBox(), this.reqCRS, th ); 583 } 584 this.graph.setClip( 0, 0, this.request.getWidth(), this.request.getHeight() ); 585 if ( !this.request.getTransparency() ) { 586 this.graph.setColor( this.request.getBGColor() ); 587 this.graph.fillRect( 0, 0, this.request.getWidth(), this.request.getHeight() ); 588 } 589 if ( map != null ) { 590 Theme[] allthemes = map.getAllThemes(); 591 map.addOptimizer( new LabelOptimizer( allthemes ) ); 592 // antialiasing must be switched of for gif output format 593 // because the antialiasing may create more than 255 colors 594 // in the map/image, even just a few colors are defined in 595 // the styles 596 if ( !this.configuration.getDeegreeParams().isAntiAliased() ) { 597 ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_ANTIALIASING, 598 RenderingHints.VALUE_ANTIALIAS_ON ); 599 ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, 600 RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); 601 } 602 map.paint( this.graph ); 603 } 604 } catch ( Exception e ) { 605 LOG.logError( e.getMessage(), e ); 606 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() ); 607 } 608 } 609 610 // print a copyright note at the left lower corner of the map 611 printCopyright( this.graph, this.request.getHeight() ); 612 613 } 614 615 /** 616 * prints a copyright note at left side of the map bottom. The copyright note will be extracted 617 * from the WMS capabilities/configuration 618 * 619 * @param g 620 * graphic context of the map 621 * @param heigth 622 * height of the map in pixel 623 */ 624 private void printCopyright( Graphics g, int heigth ) { 625 626 WMPSDeegreeParams dp = this.configuration.getDeegreeParams(); 627 String copyright = dp.getCopyright(); 628 if ( this.copyrightImg != null ) { 629 g.drawImage( this.copyrightImg, 8, heigth - this.copyrightImg.getHeight() - 5, null ); 630 } else { 631 if ( copyright != null ) { 632 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) ); 633 g.setColor( Color.BLACK ); 634 g.drawString( copyright, 8, heigth - 15 ); 635 g.drawString( copyright, 10, heigth - 15 ); 636 g.drawString( copyright, 8, heigth - 13 ); 637 g.drawString( copyright, 10, heigth - 13 ); 638 g.setColor( Color.WHITE ); 639 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) ); 640 g.drawString( copyright, 9, heigth - 14 ); 641 // g.dispose(); 642 } 643 } 644 } 645 }