001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/DefaultGetMapHandler.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; 037 038 import static java.awt.image.BufferedImage.TYPE_BYTE_INDEXED; 039 import static java.util.Arrays.asList; 040 import static java.util.regex.Pattern.compile; 041 import static javax.media.jai.operator.ColorQuantizerDescriptor.MEDIANCUT; 042 import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84; 043 import static org.deegree.framework.util.CollectionUtils.find; 044 import static org.deegree.i18n.Messages.get; 045 import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetMapResponse; 046 047 import java.awt.Color; 048 import java.awt.Font; 049 import java.awt.Graphics; 050 import java.awt.Graphics2D; 051 import java.awt.RenderingHints; 052 import java.awt.image.BufferedImage; 053 import java.awt.image.IndexColorModel; 054 import java.awt.image.WritableRaster; 055 import java.util.ArrayList; 056 import java.util.HashMap; 057 import java.util.LinkedList; 058 import java.util.List; 059 import java.util.concurrent.Callable; 060 import java.util.concurrent.CancellationException; 061 import java.util.regex.Pattern; 062 063 import javax.media.jai.RenderedOp; 064 import javax.media.jai.operator.BandSelectDescriptor; 065 import javax.media.jai.operator.ColorQuantizerDescriptor; 066 067 import org.apache.batik.svggen.SVGGraphics2D; 068 import org.deegree.framework.concurrent.ExecutionFinishedEvent; 069 import org.deegree.framework.concurrent.Executor; 070 import org.deegree.framework.log.ILogger; 071 import org.deegree.framework.log.LoggerFactory; 072 import org.deegree.framework.util.ImageUtils; 073 import org.deegree.framework.util.MapUtils; 074 import org.deegree.framework.util.MimeTypeMapper; 075 import org.deegree.framework.util.CollectionUtils.Predicate; 076 import org.deegree.graphics.MapFactory; 077 import org.deegree.graphics.Theme; 078 import org.deegree.graphics.optimizers.LabelOptimizer; 079 import org.deegree.graphics.sld.AbstractLayer; 080 import org.deegree.graphics.sld.AbstractStyle; 081 import org.deegree.graphics.sld.NamedLayer; 082 import org.deegree.graphics.sld.NamedStyle; 083 import org.deegree.graphics.sld.StyledLayerDescriptor; 084 import org.deegree.graphics.sld.UserLayer; 085 import org.deegree.graphics.sld.UserStyle; 086 import org.deegree.i18n.Messages; 087 import org.deegree.model.crs.CRSFactory; 088 import org.deegree.model.crs.CoordinateSystem; 089 import org.deegree.model.crs.GeoTransformer; 090 import org.deegree.model.spatialschema.Envelope; 091 import org.deegree.model.spatialschema.Geometry; 092 import org.deegree.model.spatialschema.GeometryFactory; 093 import org.deegree.ogcbase.InvalidSRSException; 094 import org.deegree.ogcwebservices.InconsistentRequestException; 095 import org.deegree.ogcwebservices.InvalidParameterValueException; 096 import org.deegree.ogcwebservices.OGCWebServiceException; 097 import org.deegree.ogcwebservices.OGCWebServiceResponse; 098 import org.deegree.ogcwebservices.wms.GetMapServiceInvokerForNL.WMSExceptionFromWCS; 099 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint; 100 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 101 import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource; 102 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType; 103 import org.deegree.ogcwebservices.wms.configuration.WMSConfiguration_1_3_0; 104 import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams; 105 import org.deegree.ogcwebservices.wms.operation.GetMap; 106 import org.deegree.ogcwebservices.wms.operation.GetMapResult; 107 import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory; 108 import org.deegree.ogcwebservices.wms.operation.GetMap.Layer; 109 import org.w3c.dom.Element; 110 111 /** 112 * 113 * 114 * @version $Revision: 18195 $ 115 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 116 */ 117 public class DefaultGetMapHandler implements GetMapHandler { 118 119 private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class ); 120 121 private GetMap request = null; 122 123 // private Object[] themes = null; 124 125 private double scale = 0; 126 127 private CoordinateSystem reqCRS = null; 128 129 private WMSConfigurationType configuration = null; 130 131 private BufferedImage copyrightImg = null; 132 133 boolean version130 = false; 134 135 HashMap<String, String> sqls; 136 137 private GeoTransformer transformToWGS84; 138 139 // could be improved, I'm sure 140 private static final Pattern CHECKSQL = compile( "(^insert|^update|^delete|.*[ ()]insert[() ]|.*[() ]update[() ]|.*[() ]delete[() ]).*" ); 141 142 /** 143 * Creates a new GetMapHandler object. 144 * 145 * @param configuration 146 * @param request 147 * request to perform 148 */ 149 public DefaultGetMapHandler( WMSConfigurationType configuration, GetMap request ) { 150 this.request = request; 151 this.configuration = configuration; 152 153 try { 154 // get copyright image if possible 155 copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyRight() ); 156 } catch ( Exception e ) { 157 // don't use copyright 158 } 159 160 } 161 162 /** 163 * returns the configuration used by the handler 164 * 165 * @return the configuration document 166 */ 167 public WMSConfigurationType getConfiguration() { 168 return configuration; 169 } 170 171 /** 172 * performs a GetMap request and returns the result encapsulated within a <tt>GetMapResult</tt> object. 173 * <p> 174 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it 175 * impossible to return a result. If something went wrong performing the request (none fatal error) The exception 176 * shall be encapsulated within the response object to be returned to the client as requested (GetMap-Request 177 * EXCEPTION-Parameter). 178 * 179 * @return response to the GetMap response 180 */ 181 public OGCWebServiceResponse performGetMap() 182 throws OGCWebServiceException { 183 184 // get templates, check templates 185 String sqltemplates = request.getVendorSpecificParameter( "SQLTEMPLATES" ); 186 if ( sqltemplates != null ) { 187 LinkedList<String> sqls = new LinkedList<String>(); 188 sqls.addAll( asList( sqltemplates.split( ";" ) ) ); 189 if ( sqls.size() != request.getLayers().length ) { 190 throw new InvalidParameterValueException( get( "WMS_INVALID_SQL_TEMPLATE_NUMBER" ) ); 191 } 192 this.sqls = new HashMap<String, String>( sqls.size() ); 193 for ( int i = 0; i < request.getLayers().length; ++i ) { 194 String sql = sqls.peek(); 195 if ( !sql.equals( "default" ) ) { 196 if ( CHECKSQL.matcher( sql.toLowerCase() ).matches() ) { 197 throw new InvalidParameterValueException( 198 get( "WMS_INVALID_SQL_TEMPLATE_NO_TRANSACTION_PLEASE" ) ); 199 } 200 String name = request.getLayers()[i].getName(); 201 // ok iff all database data sources have custom sql allowed 202 for ( AbstractDataSource ds : configuration.getLayer( name ).getDataSource() ) { 203 if ( ds instanceof DatabaseDataSource ) { 204 if ( !( (DatabaseDataSource) ds ).isCustomSQLAllowed() ) { 205 throw new InvalidParameterValueException( 206 get( 207 "WMS_SQL_TEMPLATE_NOT_ALLOWED_FOR_LAYER", 208 name ) ); 209 } 210 } 211 } 212 213 this.sqls.put( name, sqls.poll() ); 214 } 215 } 216 } 217 218 List<Callable<Object>> themes = constructThemes(); 219 220 Executor executor = Executor.getInstance(); 221 try { 222 List<ExecutionFinishedEvent<Object>> results; 223 results = executor.performSynchronously( themes, configuration.getDeegreeParams().getRequestTimeLimit() ); 224 225 GetMapResult res = renderMap( results ); 226 return res; 227 } catch ( InterruptedException e ) { 228 LOG.logError( e.getMessage(), e ); 229 String s = Messages.getMessage( "WMS_WAITING" ); 230 throw new OGCWebServiceException( getClass().getName(), s ); 231 } 232 } 233 234 /** 235 * @return a list of callables that construct the maps to be painted 236 * @throws OGCWebServiceException 237 */ 238 public List<Callable<Object>> constructThemes() 239 throws OGCWebServiceException { 240 // some initialization is done here because the constructor is called by reflection 241 // and the exceptions won't be properly handled in that case 242 if ( reqCRS == null ) { 243 try { 244 reqCRS = CRSFactory.create( request.getSrs().toLowerCase() ); 245 } catch ( Exception e ) { 246 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) ); 247 } 248 } 249 250 version130 = "1.3.0".equals( request.getVersion() ); 251 252 // exceeds the max allowed map width ? 253 int maxWidth = configuration.getDeegreeParams().getMaxMapWidth(); 254 if ( ( maxWidth != 0 ) && ( request.getWidth() > maxWidth ) ) { 255 throw new InconsistentRequestException( Messages.getMessage( "WMS_EXCEEDS_WIDTH", new Integer( maxWidth ) ) ); 256 } 257 258 // exceeds the max allowed map height ? 259 int maxHeight = configuration.getDeegreeParams().getMaxMapHeight(); 260 if ( ( maxHeight != 0 ) && ( request.getHeight() > maxHeight ) ) { 261 throw new InconsistentRequestException( 262 Messages.getMessage( "WMS_EXCEEDS_HEIGHT", new Integer( maxHeight ) ) ); 263 } 264 265 try { 266 double pixelSize = 1; 267 if ( version130 ) { 268 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator 269 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter 270 pixelSize = MapUtils.DEFAULT_PIXEL_SIZE; 271 } 272 273 scale = MapUtils.calcScale( request.getWidth(), request.getHeight(), request.getBoundingBox(), reqCRS, 274 pixelSize ); 275 276 LOG.logInfo( "OGC WMS scale: " + scale ); 277 } catch ( Exception e ) { 278 LOG.logError( e.getMessage(), e ); 279 throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC" ) ); 280 } 281 282 Layer[] ls = request.getLayers(); 283 284 // if 1.3.0, check for maximum allowed layers 285 if ( version130 ) { 286 WMSConfiguration_1_3_0 cfg = (WMSConfiguration_1_3_0) configuration; 287 if ( ls.length > cfg.getLayerLimit() ) { 288 String ms = Messages.getMessage( "WMS_EXCEEDS_NUMBER", new Integer( cfg.getLayerLimit() ) ); 289 throw new InconsistentRequestException( ms ); 290 } 291 } 292 293 Layer[] oldLayers = ls; 294 ls = validateLayers( ls ); 295 296 LOG.logDebug( "Validated " + ls.length + " layers." ); 297 298 StyledLayerDescriptor sld = toSLD( oldLayers, request.getStyledLayerDescriptor() ); 299 300 AbstractLayer[] layers = sld.getLayers(); 301 302 LOG.logDebug( "After SLD consideration, found " + layers.length + " layers." ); 303 304 Envelope wgs84bbox = request.getBoundingBox(); 305 if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) { 306 // transform the bounding box of the request to EPSG:4326 307 transformToWGS84 = new GeoTransformer( CRSFactory.create( WGS84 ) ); 308 try { 309 wgs84bbox = transformToWGS84.transform( wgs84bbox, reqCRS ); 310 } catch ( Exception e ) { 311 // should never happen 312 LOG.logError( "Could not validate WMS datasource area", e ); 313 } 314 315 } 316 317 List<Callable<Object>> themes = new LinkedList<Callable<Object>>(); 318 for ( int i = 0; i < layers.length; i++ ) { 319 320 if ( layers[i] instanceof NamedLayer ) { 321 String styleName = null; 322 if ( i < request.getLayers().length ) { 323 styleName = request.getLayers()[i].getStyleName(); 324 } 325 invokeNamedLayer( layers[i], styleName, themes, wgs84bbox ); 326 } else { 327 double sc = scale; 328 if ( !version130 ) { 329 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator 330 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter 331 sc = scale / MapUtils.DEFAULT_PIXEL_SIZE; 332 } 333 themes.add( new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], sc ) ); 334 } 335 } 336 337 return themes; 338 } 339 340 /** 341 * this methods validates layer in two ways:<br> 342 * a) are layers available from the current WMS<br> 343 * b) If a layer is selected that includes other layers determine all its sublayers having <Name>s and return them 344 * instead 345 * 346 * @param ls 347 * @return the layers 348 * @throws LayerNotDefinedException 349 * @throws InvalidSRSException 350 */ 351 private Layer[] validateLayers( Layer[] ls ) 352 throws LayerNotDefinedException, InvalidSRSException { 353 354 List<Layer> layer = new ArrayList<Layer>( ls.length ); 355 for ( int i = 0; i < ls.length; i++ ) { 356 org.deegree.ogcwebservices.wms.capabilities.Layer l = configuration.getLayer( ls[i].getName() ); 357 358 if ( l == null ) { 359 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", ls[i].getName() ) ); 360 } 361 362 validateSRS( l.getSrs(), ls[i].getName() ); 363 364 layer.add( ls[i] ); 365 if ( l.getLayer() != null ) { 366 layer = addNestedLayers( l.getLayer(), ls[i].getStyleName(), layer ); 367 } 368 } 369 370 return layer.toArray( new Layer[layer.size()] ); 371 } 372 373 /** 374 * adds all direct and none direct sub-layers of the passed WMS capabilities layer as 375 * 376 * @see GetMap.Layer to the passed list. 377 * @param list 378 * @return all sublayers 379 * @throws InvalidSRSException 380 */ 381 private List<Layer> addNestedLayers( org.deegree.ogcwebservices.wms.capabilities.Layer[] ll, String styleName, 382 List<Layer> list ) 383 throws InvalidSRSException { 384 385 for ( int j = 0; j < ll.length; j++ ) { 386 if ( ll[j].getName() != null ) { 387 String name = ll[j].getName(); 388 validateSRS( ll[j].getSrs(), name ); 389 list.add( GetMap.createLayer( name, styleName ) ); 390 } 391 if ( ll[j].getLayer() != null ) { 392 list = addNestedLayers( ll[j].getLayer(), styleName, list ); 393 } 394 395 } 396 return list; 397 } 398 399 /** 400 * throws an exception if the requested SRS is not be supported by the passed layer (name) 401 * 402 * @param srs 403 * @param name 404 * @throws InvalidSRSException 405 */ 406 private void validateSRS( String[] srs, String name ) 407 throws InvalidSRSException { 408 boolean validSRS = false; 409 for ( int k = 0; k < srs.length; k++ ) { 410 validSRS = srs[k].equalsIgnoreCase( reqCRS.getIdentifier() ); 411 if ( validSRS ) 412 break; 413 } 414 if ( !validSRS ) { 415 String s = Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", reqCRS.getIdentifier(), name ); 416 throw new InvalidSRSException( s ); 417 } 418 } 419 420 private void invokeNamedLayer( AbstractLayer layer, String styleName, List<Callable<Object>> tasks, 421 Envelope wgs84bbox ) 422 throws OGCWebServiceException { 423 org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layer.getName() ); 424 425 LOG.logDebug( "Invoked layer " + layer.getName() ); 426 if ( validate( lay, layer.getName(), wgs84bbox ) ) { 427 UserStyle us = getStyles( (NamedLayer) layer, styleName ); 428 AbstractDataSource[] ds = lay.getDataSource(); 429 430 if ( ds.length == 0 ) { 431 LOG.logDebug( "No datasources for layer " + layer.getName() ); 432 } else { 433 for ( int j = 0; j < ds.length; j++ ) { 434 435 LOG.logDebug( "Invoked datasource " + ds[j].getClass() + " for layer " + layer.getName() ); 436 437 ScaleHint scaleHint = ds[j].getScaleHint(); 438 if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax() 439 && isValidArea( ds[j].getValidArea() ) ) { 440 double sc = scale; 441 if ( !version130 ) { 442 // required because for WMS 1.3.0 'scale' represents the 443 // ScaleDenominator 444 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in 445 // meter 446 sc = scale / MapUtils.DEFAULT_PIXEL_SIZE; 447 } 448 GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, (NamedLayer) layer, ds[j], 449 us, sc ); 450 tasks.add( si ); 451 } else { 452 LOG.logDebug( "Not showing layer " + layer.getName() + " due to scale" ); 453 } 454 } 455 } 456 } else { 457 // using side effects for everything is great: 458 // when layers are eg. out of the bounding box, the use of invalid styles was not checked 459 // so let's do it here... 460 getStyles( (NamedLayer) layer, styleName ); 461 } 462 } 463 464 /** 465 * returns true if the requested boundingbox intersects with the valid area of a datasource 466 * 467 * @param validArea 468 */ 469 private boolean isValidArea( Geometry validArea ) { 470 471 if ( validArea != null ) { 472 try { 473 Envelope env = request.getBoundingBox(); 474 Geometry geom = GeometryFactory.createSurface( env, reqCRS ); 475 if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) { 476 // if requested CRS is not identical to the CRS of the valid area 477 // a transformation must be performed before intersection can 478 // be checked 479 GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() ); 480 geom = gt.transform( geom ); 481 } 482 return geom.intersects( validArea ); 483 } catch ( Exception e ) { 484 // should never happen 485 LOG.logError( "Could not validate WMS datasource area", e ); 486 } 487 } 488 return true; 489 } 490 491 /** 492 * creates a StyledLayerDocument containing all requested layer, nested layers if required and assigend styles. Not 493 * considered are nested layers for mixed requests (LAYERS- and SLD(_BODY)- parameter has been defined) 494 * 495 * @param layers 496 * @param inSLD 497 * @return a combined SLD object 498 * @throws InvalidSRSException 499 */ 500 private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD ) 501 throws InvalidSRSException { 502 StyledLayerDescriptor sld = null; 503 504 if ( layers != null && layers.length > 0 && inSLD == null ) { 505 // if just a list of layers has been requested 506 507 // create a SLD from the requested LAYERS and assigned STYLES 508 List<AbstractLayer> al = new ArrayList<AbstractLayer>( layers.length * 2 ); 509 for ( int i = 0; i < layers.length; i++ ) { 510 AbstractStyle[] as = new AbstractStyle[] { new NamedStyle( layers[i].getStyleName() ) }; 511 al.add( new NamedLayer( layers[i].getName(), null, as ) ); 512 513 // collect all named nested layers 514 org.deegree.ogcwebservices.wms.capabilities.Layer lla; 515 lla = configuration.getLayer( layers[i].getName() ); 516 List<GetMap.Layer> list = new ArrayList<GetMap.Layer>(); 517 addNestedLayers( lla.getLayer(), layers[i].getStyleName(), list ); 518 519 // add nested layers to list of layers to be handled 520 for ( int j = 0; j < list.size(); j++ ) { 521 GetMap.Layer nestedLayer = list.get( j ); 522 as = new AbstractStyle[] { new NamedStyle( nestedLayer.getStyleName() ) }; 523 al.add( new NamedLayer( nestedLayer.getName(), null, as ) ); 524 } 525 } 526 sld = new StyledLayerDescriptor( al.toArray( new AbstractLayer[al.size()] ), "1.0.0" ); 527 } else if ( layers != null && layers.length > 0 && inSLD != null ) { 528 // if layers not null and sld is not null then SLD layers just be 529 // considered if present in the layers list 530 // TODO 531 // layer with nested layers are not handled correctly and I think 532 // it really causes a lot of problems to use them in such a way 533 // because the style assigned to the mesting layer must be 534 // applicable for all nested layers. 535 List<String> list = new ArrayList<String>(); 536 for ( int i = 0; i < layers.length; i++ ) { 537 list.add( layers[i].getName() ); 538 } 539 540 List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 ); 541 542 for ( final GetMap.Layer lay : layers ) { 543 NamedLayer sldLay = find( inSLD.getNamedLayers(), new Predicate<NamedLayer>() { 544 public boolean eval( NamedLayer t ) { 545 return t.getName().equals( lay.getName() ); 546 } 547 } ); 548 549 AbstractStyle[] as; 550 if ( sldLay == null ) { 551 as = new AbstractStyle[] { new NamedStyle( lay.getStyleName() ) }; 552 newList.add( new NamedLayer( lay.getName(), null, as ) ); 553 } else { 554 newList.add( sldLay ); 555 } 556 557 // finally, don't forget the user layers 558 for ( UserLayer ul : inSLD.getUserLayers() ) { 559 newList.add( ul ); 560 } 561 } 562 563 AbstractLayer[] al = new AbstractLayer[newList.size()]; 564 sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() ); 565 } else { 566 // if no layers but a SLD is defined ... 567 AbstractLayer[] as = inSLD.getLayers(); 568 for ( AbstractLayer l : as ) { 569 addNestedLayers( l, inSLD ); 570 } 571 572 sld = inSLD; 573 } 574 575 return sld; 576 } 577 578 // adds the nested layers to the sld 579 private void addNestedLayers( AbstractLayer l, StyledLayerDescriptor sld ) { 580 if ( !( l instanceof NamedLayer ) ) { 581 return; 582 } 583 if ( configuration.getLayer( l.getName() ) == null ) { 584 return; 585 } 586 587 org.deegree.ogcwebservices.wms.capabilities.Layer[] ls; 588 ls = configuration.getLayer( l.getName() ).getLayer(); 589 for ( org.deegree.ogcwebservices.wms.capabilities.Layer lay : ls ) { 590 NamedStyle sty = new NamedStyle( lay.getStyles()[0].getName() ); 591 AbstractStyle[] newSty = new AbstractStyle[] { sty }; 592 NamedLayer newLay = new NamedLayer( lay.getName(), null, newSty ); 593 sld.addLayer( newLay ); 594 } 595 } 596 597 /** 598 * returns the <tt>UserStyle</tt>s assigned to a named layer 599 * 600 * @param sldLayer 601 * layer to get the styles for 602 * @param styleName 603 * requested stylename (from the KVP encoding) 604 */ 605 private UserStyle getStyles( NamedLayer sldLayer, String styleName ) 606 throws OGCWebServiceException { 607 608 AbstractStyle[] styles = sldLayer.getStyles(); 609 UserStyle us = null; 610 611 // to avoid retrieving the layer again for each style 612 org.deegree.ogcwebservices.wms.capabilities.Layer layer = null; 613 layer = configuration.getLayer( sldLayer.getName() ); 614 int i = 0; 615 while ( us == null && i < styles.length ) { 616 if ( styles[i] instanceof NamedStyle ) { 617 // styles will be taken from the WMS's style repository 618 us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer ); 619 } else { 620 // if the requested style fits the name of the defined style or 621 // if the defined style is marked as default and the requested 622 // style if 'default' the condition is true. This includes that 623 // if more than one style with the same name or more than one 624 // style is marked as default always the first will be choosen 625 if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) ) 626 || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) { 627 us = (UserStyle) styles[i]; 628 } 629 } 630 i++; 631 } 632 if ( us == null ) { 633 // this may happens if the SLD contains a named layer but not 634 // a style! yes this is valid according to SLD spec 1.0.0 635 us = getPredefinedStyle( styleName, sldLayer.getName(), layer ); 636 } 637 return us; 638 } 639 640 /** 641 * 642 * @param styleName 643 * @param layerName 644 * @param layer 645 * @return the style 646 * @throws StyleNotDefinedException 647 */ 648 public UserStyle getPredefinedStyle( String styleName, String layerName, 649 org.deegree.ogcwebservices.wms.capabilities.Layer layer ) 650 throws StyleNotDefinedException { 651 UserStyle us = null; 652 if ( "default".equals( styleName ) ) { 653 us = layer.getStyle( styleName ); 654 } 655 656 if ( us == null ) { 657 if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" ) 658 || styleName.equals( "default" ) ) { 659 styleName = "default:" + layerName; 660 } 661 } 662 663 us = layer.getStyle( styleName ); 664 665 if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) { 666 String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer ); 667 throw new StyleNotDefinedException( s ); 668 } 669 return us; 670 } 671 672 /** 673 * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will 674 * be thrown. If the layer matches the request, but isn't able to deviever data for the requested area and/or scale 675 * false will be returned. If the layer matches the request and contains data for the requested area and/or scale 676 * true will be returned. 677 * 678 * @param layer 679 * layer as defined at the capabilities/configuration 680 * @param name 681 * name of the layer (must be submitted separately because the layer parameter can be <tt>null</tt> 682 * @param wgs84bbox 683 * the wgs84 bbox of the request 684 */ 685 private boolean validate( org.deegree.ogcwebservices.wms.capabilities.Layer layer, String name, Envelope wgs84bbox ) 686 throws OGCWebServiceException { 687 688 // check if layer is available 689 if ( layer == null ) { 690 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", name ) ); 691 } 692 693 // check bounding box 694 try { 695 Envelope layerBbox = layer.getLatLonBoundingBox(); 696 if ( !wgs84bbox.intersects( layerBbox ) ) { 697 LOG.logDebug( "Not showing layer because the request is out of the bounding box." ); 698 return false; 699 } 700 701 } catch ( Exception e ) { 702 LOG.logError( e.getMessage(), e ); 703 throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) ); 704 } 705 706 return true; 707 } 708 709 /** 710 * renders the map from the <tt>DisplayElement</tt>s 711 * 712 * @param results 713 * @return a result object suitable for further processing 714 * 715 * @throws OGCWebServiceException 716 */ 717 public GetMapResult renderMap( List<ExecutionFinishedEvent<Object>> results ) 718 throws OGCWebServiceException { 719 720 OGCWebServiceException exce = null; 721 722 ArrayList<Object> list = new ArrayList<Object>( 50 ); 723 for ( ExecutionFinishedEvent<Object> evt : results ) { 724 Object o = null; 725 726 // exception handling might be handled in a better way 727 try { 728 o = evt.getResult(); 729 } catch ( CancellationException e ) { 730 exce = new OGCWebServiceException( getClass().getName(), e.toString() ); 731 } catch ( OGCWebServiceException e ) { 732 throw e; 733 } catch ( Throwable e ) { 734 exce = new OGCWebServiceException( getClass().getName(), e.toString() ); 735 } 736 737 if ( o instanceof WMSExceptionFromWCS ) { 738 if ( results.size() == 1 ) { 739 exce = ( (WMSExceptionFromWCS) o ).wrapped; 740 o = exce; 741 } else { 742 o = null; 743 } 744 } 745 if ( o instanceof Exception ) { 746 exce = new OGCWebServiceException( getClass().getName(), o.toString() ); 747 } 748 if ( o instanceof OGCWebServiceException ) { 749 exce = (OGCWebServiceException) o; 750 break; 751 } 752 if ( o != null ) { 753 list.add( o ); 754 } 755 } 756 757 return render( list.toArray( new Theme[list.size()] ), exce ); 758 } 759 760 /** 761 * @param themes 762 * @param exce 763 * @return a result object suitable for further processing elsewhere 764 * @throws InvalidSRSException 765 */ 766 public GetMapResult render( Theme[] themes, OGCWebServiceException exce ) 767 throws InvalidSRSException { 768 // some initialization is done here because the constructor is called by reflection 769 // and the exceptions won't be properly handled in that case 770 // NOTE that it has to be repeated here in case someone wants to use this method only! 771 if ( reqCRS == null ) { 772 try { 773 reqCRS = CRSFactory.create( request.getSrs().toLowerCase() ); 774 } catch ( Exception e ) { 775 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) ); 776 } 777 } 778 779 GetMapResult response = null; 780 781 String mime = MimeTypeMapper.toMimeType( request.getFormat() ); 782 783 if ( configuration.getDeegreeParams().getDefaultPNGFormat() != null && mime.equalsIgnoreCase( "image/png" ) ) { 784 mime = configuration.getDeegreeParams().getDefaultPNGFormat(); 785 } 786 787 // get target object for rendering 788 Object target = GraphicContextFactory.createGraphicTarget( mime, request.getWidth(), request.getHeight() ); 789 790 // get graphic context of the target 791 Graphics g = GraphicContextFactory.createGraphicContext( mime, target ); 792 if ( exce == null ) { 793 // only if no exception occured 794 try { 795 org.deegree.graphics.MapView map = null; 796 if ( themes.length > 0 ) { 797 map = MapFactory.createMapView( "deegree WMS", request.getBoundingBox(), reqCRS, themes, 798 MapUtils.DEFAULT_PIXEL_SIZE ); 799 } 800 g.setClip( 0, 0, request.getWidth(), request.getHeight() ); 801 802 if ( !request.getTransparency() ) { 803 if ( g instanceof Graphics2D ) { 804 // this ensures real clearing (rendering modifies the color ever so 805 // slightly) 806 ( (Graphics2D) g ).setBackground( request.getBGColor() ); 807 g.clearRect( 0, 0, request.getWidth(), request.getHeight() ); 808 } else { 809 g.setColor( request.getBGColor() ); 810 g.fillRect( 0, 0, request.getWidth(), request.getHeight() ); 811 } 812 } 813 814 if ( map != null ) { 815 Theme[] thms = map.getAllThemes(); 816 map.addOptimizer( new LabelOptimizer( thms ) ); 817 // antialiasing must be switched of for gif output format 818 // because the antialiasing may create more than 255 colors 819 // in the map/image, even just a few colors are defined in 820 // the styles 821 if ( !request.getFormat().equalsIgnoreCase( "image/gif" ) ) { 822 if ( configuration.getDeegreeParams().isAntiAliased() ) { 823 ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_ANTIALIASING, 824 RenderingHints.VALUE_ANTIALIAS_ON ); 825 ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, 826 RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); 827 } 828 } 829 map.paint( g ); 830 } 831 } catch ( Exception e ) { 832 LOG.logError( e.getMessage(), e ); 833 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() ); 834 } 835 } 836 837 // print a copyright note at the left lower corner of the map 838 printCopyright( g, request.getHeight() ); 839 840 if ( mime.equals( "image/svg+xml" ) || mime.equals( "image/svg xml" ) ) { 841 Element root = ( (SVGGraphics2D) g ).getRoot(); 842 root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); 843 response = WMSProtocolFactory.createGetMapResponse( request, exce, root ); 844 } else { 845 846 BufferedImage img = (BufferedImage) target; 847 848 if ( mime.equals( "image/png; mode=8bit" ) ) { 849 RenderedOp torgb = BandSelectDescriptor.create( img, new int[] { 0, 1, 2 }, null ); 850 851 torgb = ColorQuantizerDescriptor.create( torgb, MEDIANCUT, 254, null, null, null, null, null ); 852 853 WritableRaster data = torgb.getAsBufferedImage().getRaster(); 854 855 IndexColorModel model = (IndexColorModel) torgb.getColorModel(); 856 byte[] reds = new byte[256]; 857 byte[] greens = new byte[256]; 858 byte[] blues = new byte[256]; 859 byte[] alphas = new byte[256]; 860 model.getReds( reds ); 861 model.getGreens( greens ); 862 model.getBlues( blues ); 863 // note that this COULD BE OPTIMIZED to SUPPORT EG HALF TRANSPARENT PIXELS for PNG-8! 864 // It's not true that PNG-8 does not support this! Try setting the value to eg. 128 here and see what 865 // you'll get... 866 for ( int i = 0; i < 254; ++i ) { 867 alphas[i] = -1; 868 } 869 alphas[255] = 0; 870 IndexColorModel newModel = new IndexColorModel( 8, 256, reds, greens, blues, alphas ); 871 872 // yeah, double memory, but it was the only way I could find (I could be blind...) 873 BufferedImage res = new BufferedImage( torgb.getWidth(), torgb.getHeight(), TYPE_BYTE_INDEXED, newModel ); 874 res.setData( data ); 875 876 // do it the hard way as the OR operation would destroy the channels 877 for ( int y = 0; y < img.getHeight(); ++y ) { 878 for ( int x = 0; x < img.getWidth(); ++x ) { 879 if ( img.getRGB( x, y ) == 0 ) { 880 res.setRGB( x, y, 0 ); 881 } 882 } 883 } 884 885 target = res; 886 } 887 888 response = createGetMapResponse( request, exce, target ); 889 } 890 g.dispose(); 891 892 return response; 893 } 894 895 // works, but only with some bogus pixel that means "transparency" 896 // private static BufferedImage makeTransparent( BufferedImage img ) { 897 // IndexColorModel cm = (IndexColorModel) img.getColorModel(); 898 // WritableRaster raster = img.getRaster(); 899 // int pixel = raster.getSample( 0, 0, 0 ); 900 // int size = cm.getMapSize(); 901 // byte[] reds = new byte[size]; 902 // byte[] greens = new byte[size]; 903 // byte[] blues = new byte[size]; 904 // cm.getReds( reds ); 905 // cm.getGreens( greens ); 906 // cm.getBlues( blues ); 907 // return new BufferedImage( new IndexColorModel( 8, size, reds, greens, blues, pixel ), raster, 908 // img.isAlphaPremultiplied(), null ); 909 // } 910 911 /** 912 * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS 913 * capabilities/configuration 914 * 915 * @param g 916 * graphic context of the map 917 * @param heigth 918 * height of the map in pixel 919 */ 920 private void printCopyright( Graphics g, int heigth ) { 921 WMSDeegreeParams dp = configuration.getDeegreeParams(); 922 String copyright = dp.getCopyRight(); 923 if ( copyrightImg != null ) { 924 g.drawImage( copyrightImg, 8, heigth - copyrightImg.getHeight() - 5, null ); 925 } else { 926 if ( copyright != null ) { 927 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) ); 928 g.setColor( Color.BLACK ); 929 g.drawString( copyright, 8, heigth - 15 ); 930 g.drawString( copyright, 10, heigth - 15 ); 931 g.drawString( copyright, 8, heigth - 13 ); 932 g.drawString( copyright, 10, heigth - 13 ); 933 g.setColor( Color.WHITE ); 934 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) ); 935 g.drawString( copyright, 9, heigth - 14 ); 936 } 937 } 938 939 } 940 941 /** 942 * @return the request that is being handled 943 */ 944 protected GetMap getRequest() { 945 return request; 946 } 947 948 /** 949 * @return the requests coordinate system 950 */ 951 protected CoordinateSystem getRequestCRS() { 952 return reqCRS; 953 } 954 955 }