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 }