001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }