001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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: 18332 $, $Date: 2009-07-07 10:27:31 +0200 (Di, 07. Jul 2009) $
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 protected 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
468 return image;
469 }
470
471 /**
472 * Sets a <tt>BufferedImage</tt> representing this object. The image respects the 'Opacity',
473 * 'Size' and 'Rotation' parameters.
474 * <p>
475 *
476 * @param bufferedImage
477 * BufferedImage to be set
478 */
479 public void setAsImage( BufferedImage bufferedImage ) {
480 image = bufferedImage;
481 }
482
483 /**
484 * exports the content of the Graphic as XML formated String
485 *
486 * @return xml representation of the Graphic
487 */
488 public String exportAsXML() {
489
490 StringBuffer sb = new StringBuffer( 1000 );
491 sb.append( "<Graphic>" );
492 for ( int i = 0; i < marksAndExtGraphics.size(); i++ ) {
493 sb.append( ( (Marshallable) marksAndExtGraphics.get( i ) ).exportAsXML() );
494 }
495 if ( opacity != null ) {
496 sb.append( "<Opacity>" );
497 sb.append( ( (Marshallable) opacity ).exportAsXML() );
498 sb.append( "</Opacity>" );
499 }
500 if ( size != null ) {
501 sb.append( "<Size>" );
502 sb.append( ( (Marshallable) size ).exportAsXML() );
503 sb.append( "</Size>" );
504 }
505 if ( rotation != null ) {
506 sb.append( "<Rotation>" );
507 sb.append( ( (Marshallable) rotation ).exportAsXML() );
508 sb.append( "</Rotation>" );
509 }
510 sb.append( "</Graphic>" );
511
512 return sb.toString();
513 }
514
515 }