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