001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/graphics/sld/Graphic.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.graphics.sld; 037 038 import static java.lang.Math.toRadians; 039 040 import java.awt.Graphics2D; 041 import java.awt.image.BufferedImage; 042 import java.util.ArrayList; 043 import java.util.List; 044 045 import org.deegree.framework.log.ILogger; 046 import org.deegree.framework.log.LoggerFactory; 047 import org.deegree.framework.xml.Marshallable; 048 import org.deegree.model.feature.Feature; 049 import org.deegree.model.filterencoding.FilterEvaluationException; 050 051 /** 052 * A Graphic is a "graphic symbol" with an inherent shape, color, and size. Graphics can either be 053 * referenced from an external URL in a common format (such as GIF or SVG) or may be derived from a 054 * Mark. Multiple external URLs may be referenced with the semantic that they all provide the same 055 * graphic in different formats. The "hot spot" to use for rendering at a point or the start and 056 * finish handle points to use for rendering a graphic along a line must either be inherent in the 057 * external format or are system- dependent. The default size of an image format (such as GIF) is 058 * the inherent size of the image. The default size of a format without an inherent size is 16 059 * pixels in height and the corresponding aspect in width. If a size is specified, the height of the 060 * graphic will be scaled to that size and the corresponding aspect will be used for the width. The 061 * default if neither an ExternalURL nor a Mark is specified is to use the default Mark with a size 062 * of 6 pixels. The size is in pixels and the rotation is in degrees clockwise, with 0 (default) 063 * meaning no rotation. In the case that a Graphic is derived from a font-glyph Mark, the Size 064 * specified here will be used for the final rendering. Allowed CssParameters are "opacity", "size", 065 * and "rotation". 066 * 067 * 068 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 069 * @author last edited by: $Author: lbuesching $ 070 * 071 * @version $Revision: 26665 $, $Date: 2010-09-09 20:44:38 +0200 (Do, 09 Sep 2010) $ 072 */ 073 public class Graphic implements Marshallable { 074 075 private static final ILogger LOG = LoggerFactory.getLogger( Graphic.class ); 076 077 // default values 078 /** 079 * The default Opacity = 1; 080 */ 081 public static final double OPACITY_DEFAULT = 1.0; 082 083 /** 084 * The default size is -1 085 */ 086 public static final double SIZE_DEFAULT = -1; 087 088 /** 089 * The default rotation is 0 090 */ 091 public static final double ROTATION_DEFAULT = 0.0; 092 093 private List<Object> marksAndExtGraphics = new ArrayList<Object>(); 094 095 private BufferedImage image = null; 096 097 private ParameterValueType opacity = null; 098 099 private ParameterValueType rotation = null; 100 101 private ParameterValueType size = null; 102 103 /** 104 * Creates a new <tt>Graphic</tt> instance. 105 * <p> 106 * 107 * @param marksAndExtGraphics 108 * the image will be based upon these 109 * @param opacity 110 * opacity that the resulting image will have 111 * @param size 112 * image height will be scaled to this value, respecting the proportions 113 * @param rotation 114 * image will be rotated clockwise for positive values, negative values result in 115 * anti-clockwise rotation 116 */ 117 public Graphic( Object[] marksAndExtGraphics, ParameterValueType opacity, ParameterValueType size, 118 ParameterValueType rotation ) { 119 setMarksAndExtGraphics( marksAndExtGraphics ); 120 this.opacity = opacity; 121 this.size = size; 122 this.rotation = rotation; 123 } 124 125 /** 126 * Creates a new <tt>Graphic</tt> instance based on the default <tt>Mark</tt>: a square. 127 * <p> 128 * 129 * @param opacity 130 * opacity that the resulting image will have 131 * @param size 132 * image height will be scaled to this value, respecting the proportions 133 * @param rotation 134 * image will be rotated clockwise for positive values, negative values result in 135 * anti-clockwise rotation 136 */ 137 protected Graphic( ParameterValueType opacity, ParameterValueType size, ParameterValueType rotation ) { 138 Mark[] marks = new Mark[1]; 139 marks[0] = new Mark( "square", null, null ); 140 setMarksAndExtGraphics( marks ); 141 this.opacity = opacity; 142 this.size = size; 143 this.rotation = rotation; 144 } 145 146 /** 147 * returns the ParameterValueType representation of opacity 148 * 149 * @return the ParameterValueType representation of opacity 150 */ 151 public ParameterValueType getOpacity() { 152 return opacity; 153 } 154 155 /** 156 * returns the ParameterValueType representation of rotation 157 * 158 * @return the ParameterValueType representation of rotation 159 */ 160 public ParameterValueType getRotation() { 161 return rotation; 162 } 163 164 /** 165 * returns the ParameterValueType representation of size 166 * 167 * @return the ParameterValueType representation of size 168 */ 169 public ParameterValueType getSize() { 170 return size; 171 } 172 173 /** 174 * Creates a new <tt>Graphic</tt> instance based on the default <tt>Mark</tt>: a square. 175 */ 176 protected Graphic() { 177 this( null, null, null ); 178 } 179 180 /** 181 * Returns an object-array that enables the access to the stored <tt>ExternalGraphic</tt> and 182 * <tt>Mark</tt> -instances. 183 * <p> 184 * 185 * @return contains <tt>ExternalGraphic</tt> and <tt>Mark</tt> -objects 186 * 187 */ 188 public Object[] getMarksAndExtGraphics() { 189 Object[] objects = new Object[marksAndExtGraphics.size()]; 190 return marksAndExtGraphics.toArray( objects ); 191 } 192 193 /** 194 * Sets the <tt>ExternalGraphic</tt>/ <tt>Mark<tt>-instances that the image 195 * will be based on. 196 * <p> 197 * 198 * @param object 199 * to be used as basis for the resulting image 200 */ 201 public void setMarksAndExtGraphics( Object[] object ) { 202 image = null; 203 this.marksAndExtGraphics.clear(); 204 205 if ( object != null ) { 206 for ( int i = 0; i < object.length; i++ ) { 207 marksAndExtGraphics.add( object[i] ); 208 } 209 } 210 } 211 212 /** 213 * Adds an Object to an object-array that enables the access to the stored 214 * <tt>ExternalGraphic</tt> and <tt>Mark</tt> -instances. 215 * <p> 216 * 217 * @param object 218 * to be used as basis for the resulting image 219 */ 220 public void addMarksAndExtGraphic( Object object ) { 221 marksAndExtGraphics.add( object ); 222 } 223 224 /** 225 * Removes an Object from an object-array that enables the access to the stored 226 * <tt>ExternalGraphic</tt> and <tt>Mark</tt> -instances. 227 * <p> 228 * 229 * @param object 230 * to be used as basis for the resulting image 231 */ 232 public void removeMarksAndExtGraphic( Object object ) { 233 marksAndExtGraphics.remove( marksAndExtGraphics.indexOf( object ) ); 234 } 235 236 /** 237 * The Opacity element gives the opacity to use for rendering the graphic. 238 * <p> 239 * 240 * @param feature 241 * specifies the <tt>Feature</tt> to be used for evaluation of the underlying 242 * 'sld:ParameterValueType' 243 * @return the (evaluated) value of the parameter 244 * @throws FilterEvaluationException 245 * if the evaluation fails or the value is invalid 246 */ 247 public double getOpacity( Feature feature ) 248 throws FilterEvaluationException { 249 double opacityVal = OPACITY_DEFAULT; 250 251 if ( opacity != null ) { 252 String value = opacity.evaluate( feature ); 253 254 try { 255 opacityVal = Double.parseDouble( value ); 256 } catch ( NumberFormatException e ) { 257 throw new FilterEvaluationException( "Given value for parameter 'opacity' ('" + value 258 + "') has invalid format!" ); 259 } 260 261 if ( ( opacityVal < 0.0 ) || ( opacityVal > 1.0 ) ) { 262 throw new FilterEvaluationException( "Value for parameter 'opacity' (given: '" + value 263 + "') must be between 0.0 and 1.0!" ); 264 } 265 } 266 267 return opacityVal; 268 } 269 270 /** 271 * The Opacity element gives the opacity of to use for rendering the graphic. 272 * <p> 273 * 274 * @param opacity 275 * Opacity to be set for the graphic 276 */ 277 public void setOpacity( double opacity ) { 278 ParameterValueType pvt = null; 279 pvt = StyleFactory.createParameterValueType( "" + opacity ); 280 this.opacity = pvt; 281 } 282 283 /** 284 * The Size element gives the absolute size of the graphic in pixels encoded as a floating-point 285 * number. This element is also used in other contexts than graphic size and pixel units are 286 * still used even for font size. The default size for an object is context-dependent. Negative 287 * values are not allowed. 288 * <p> 289 * 290 * @param feature 291 * specifies the <tt>Feature</tt> to be used for evaluation of the underlying 292 * 'sld:ParameterValueType' 293 * @return the (evaluated) value of the parameter 294 * @throws FilterEvaluationException 295 * if the evaluation fails or the value is invalid 296 */ 297 public double getSize( Feature feature ) 298 throws FilterEvaluationException { 299 double sizeVal = SIZE_DEFAULT; 300 301 if ( size != null ) { 302 String value = size.evaluate( feature ); 303 304 try { 305 sizeVal = Double.parseDouble( value ); 306 } catch ( NumberFormatException e ) { 307 throw new FilterEvaluationException( "Given value for parameter 'size' ('" + value 308 + "') has invalid format!" ); 309 } 310 311 if ( sizeVal <= 0.0 ) { 312 throw new FilterEvaluationException( "Value for parameter 'size' (given: '" + value 313 + "') must be greater than 0!" ); 314 } 315 } 316 317 return sizeVal; 318 } 319 320 /** 321 * @see org.deegree.graphics.sld.Graphic#getSize(Feature) <p> 322 * @param size 323 * size to be set for the graphic 324 */ 325 public void setSize( double size ) { 326 ParameterValueType pvt = null; 327 pvt = StyleFactory.createParameterValueType( "" + size ); 328 this.size = pvt; 329 } 330 331 /** 332 * @see org.deegree.graphics.sld.Graphic#getSize(Feature) <p> 333 * @param size 334 * size as ParameterValueType to be set for the graphic 335 */ 336 public void setSize( ParameterValueType size ) { 337 this.size = size; 338 } 339 340 /** 341 * The Rotation element gives the rotation of a graphic in the clockwise direction about its 342 * center point in radian, encoded as a floating- point number. Negative values mean 343 * counter-clockwise rotation. The default value is 0.0 (no rotation). 344 * <p> 345 * 346 * @param feature 347 * specifies the <tt>Feature</tt> to be used for evaluation of the underlying 348 * 'sld:ParameterValueType' 349 * @return the (evaluated) value of the parameter 350 * @throws FilterEvaluationException 351 * if the evaluation fails or the value is invalid 352 */ 353 public double getRotation( Feature feature ) 354 throws FilterEvaluationException { 355 double rotVal = ROTATION_DEFAULT; 356 357 if ( rotation != null ) { 358 String value = rotation.evaluate( feature ); 359 360 try { 361 rotVal = Double.parseDouble( value ); 362 } catch ( NumberFormatException e ) { 363 LOG.logError( e.getMessage(), e ); 364 throw new FilterEvaluationException( "Given value for parameter 'rotation' ('" + value 365 + "') has invalid format!" ); 366 } 367 } 368 369 return rotVal; 370 } 371 372 /** 373 * @see org.deegree.graphics.sld.Graphic#getRotation(Feature) <p> 374 * @param rotation 375 * rotation to be set for the graphic 376 */ 377 public void setRotation( double rotation ) { 378 ParameterValueType pvt = null; 379 pvt = StyleFactory.createParameterValueType( "" + rotation ); 380 this.rotation = pvt; 381 } 382 383 /** 384 * Returns a <tt>BufferedImage</tt> representing this object. The image respects the 'Opacity', 385 * 'Size' and 'Rotation' parameters. If the 'Size'-parameter is omitted, the height of the first 386 * <tt>ExternalGraphic</tt> is used. If there is none, the default value of 6 pixels is used. 387 * <p> 388 * 389 * @param feature 390 * 391 * @return the <tt>BufferedImage</tt> ready to be painted 392 * @throws FilterEvaluationException 393 * if the evaluation fails 394 */ 395 public BufferedImage getAsImage( Feature feature ) 396 throws FilterEvaluationException { 397 int intSizeX = (int) getSize( feature ); 398 int intSizeY = intSizeX; 399 400 // calculate the size of the first ExternalGraphic 401 int intSizeImgX = -1; 402 int intSizeImgY = -1; 403 for ( int i = 0; i < marksAndExtGraphics.size(); i++ ) { 404 Object o = marksAndExtGraphics.get( i ); 405 if ( o instanceof ExternalGraphic ) { 406 BufferedImage extImage = ( (ExternalGraphic) o ).getAsImage( intSizeX, intSizeY, feature ); 407 intSizeImgX = extImage.getWidth(); 408 intSizeImgY = extImage.getHeight(); 409 break; 410 } 411 } 412 413 if ( intSizeX < 0 ) { 414 // if size is unspecified 415 if ( intSizeImgX < 0 ) { 416 // if there are no ExternalGraphics, use default value of 6 pixels 417 intSizeX = 6; 418 intSizeY = 6; 419 } else { 420 // if there are ExternalGraphics, use width and height of the first 421 intSizeX = intSizeImgX; 422 intSizeY = intSizeImgY; 423 } 424 } else { 425 // if size is specified 426 if ( intSizeImgY < 0 ) { 427 // if there are no ExternalGraphics, use default intSizeX 428 intSizeY = intSizeX; 429 } else { 430 // if there are ExternalGraphics, use the first to find the height 431 intSizeY = (int) Math.round( ( ( (double) intSizeImgY ) / ( (double) intSizeImgX ) ) * intSizeX ); 432 } 433 } 434 435 if ( intSizeX <= 0 || intSizeY <= 0 || intSizeX > 1000 || intSizeY > 1000 ) { 436 // if there are no ExternalGraphics, use default value of 1 pixel 437 LOG.logDebug( intSizeX + " - " + intSizeY ); 438 intSizeX = 1; 439 intSizeY = 1; 440 } 441 442 image = new BufferedImage( intSizeX, intSizeY, BufferedImage.TYPE_INT_ARGB ); 443 444 Graphics2D g = (Graphics2D) image.getGraphics(); 445 g.rotate( toRadians( getRotation( feature ) ), intSizeX >> 1, intSizeY >> 1 ); 446 447 for ( int i = 0; i < marksAndExtGraphics.size(); i++ ) { 448 Object o = marksAndExtGraphics.get( i ); 449 BufferedImage extImage = null; 450 451 if ( o instanceof ExternalGraphic ) { 452 extImage = ( (ExternalGraphic) o ).getAsImage( intSizeX, intSizeY, feature ); 453 } else { 454 extImage = ( (Mark) o ).getAsImage( feature, intSizeX ); 455 } 456 457 g.drawImage( extImage, 0, 0, intSizeX, intSizeY, null ); 458 } 459 460 // use the default Mark if there are no Marks / ExternalGraphics 461 // specified at all 462 if ( marksAndExtGraphics.size() == 0 ) { 463 Mark mark = new Mark(); 464 BufferedImage extImage = mark.getAsImage( feature, intSizeX ); 465 g.drawImage( extImage, 0, 0, intSizeX, intSizeY, null ); 466 } 467 g.dispose(); 468 469 return image; 470 } 471 472 /** 473 * Sets a <tt>BufferedImage</tt> representing this object. The image respects the 'Opacity', 474 * 'Size' and 'Rotation' parameters. 475 * <p> 476 * 477 * @param bufferedImage 478 * BufferedImage to be set 479 */ 480 public void setAsImage( BufferedImage bufferedImage ) { 481 image = bufferedImage; 482 } 483 484 /** 485 * exports the content of the Graphic as XML formated String 486 * 487 * @return xml representation of the Graphic 488 */ 489 public String exportAsXML() { 490 491 StringBuffer sb = new StringBuffer( 1000 ); 492 sb.append( "<Graphic>" ); 493 for ( int i = 0; i < marksAndExtGraphics.size(); i++ ) { 494 sb.append( ( (Marshallable) marksAndExtGraphics.get( i ) ).exportAsXML() ); 495 } 496 if ( opacity != null ) { 497 sb.append( "<Opacity>" ); 498 sb.append( ( (Marshallable) opacity ).exportAsXML() ); 499 sb.append( "</Opacity>" ); 500 } 501 if ( size != null ) { 502 sb.append( "<Size>" ); 503 sb.append( ( (Marshallable) size ).exportAsXML() ); 504 sb.append( "</Size>" ); 505 } 506 if ( rotation != null ) { 507 sb.append( "<Rotation>" ); 508 sb.append( ( (Marshallable) rotation ).exportAsXML() ); 509 sb.append( "</Rotation>" ); 510 } 511 sb.append( "</Graphic>" ); 512 513 return sb.toString(); 514 } 515 516 }