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