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    }