001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/graphics/displayelements/RotatedLabel.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.displayelements;
037    
038    import java.awt.BasicStroke;
039    import java.awt.Color;
040    import java.awt.Font;
041    import java.awt.Graphics2D;
042    import java.awt.Rectangle;
043    import java.awt.TexturePaint;
044    import java.awt.font.LineMetrics;
045    import java.awt.geom.AffineTransform;
046    import java.awt.image.BufferedImage;
047    
048    import org.deegree.framework.log.ILogger;
049    import org.deegree.framework.log.LoggerFactory;
050    import org.deegree.graphics.sld.Fill;
051    import org.deegree.graphics.sld.GraphicFill;
052    import org.deegree.graphics.sld.Halo;
053    import org.deegree.model.feature.Feature;
054    import org.deegree.model.filterencoding.FilterEvaluationException;
055    
056    /**
057     * This is a rotated label with style information and screen coordinates, ready to be rendered to the view.
058     * <p>
059     * 
060     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
061     * @version $Revision: 29951 $ $Date: 2011-03-09 13:14:14 +0100 (Wed, 09 Mar 2011) $
062     */
063    
064    class RotatedLabel implements Label {
065    
066        private static final ILogger LOG = LoggerFactory.getLogger( RotatedLabel.class );
067    
068        private String caption;
069    
070        private int[] xpoints;
071    
072        private int[] ypoints;
073    
074        private double rotation;
075    
076        private double anchorPoint[];
077    
078        // width and height of the caption
079        private int w;
080    
081        // width and height of the caption
082        private int h;
083    
084        private Color color;
085    
086        private Font font;
087    
088        private int descent;
089    
090        private int ascent;
091    
092        private Halo halo;
093    
094        private Feature feature;
095    
096        private double opacity;
097    
098        /**
099         * 
100         * @param caption
101         * @param font
102         * @param color
103         * @param metrics
104         * @param feature
105         * @param halo
106         * @param x
107         * @param y
108         * @param w
109         * @param h
110         * @param rotation
111         * @param anchorPoint
112         * @param displacement
113         * @param opacity
114         */
115        RotatedLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, Halo halo, int x,
116                      int y, int w, int h, double rotation, double anchorPoint[], double[] displacement, double opacity ) {
117            this.caption = caption;
118            this.font = font;
119            this.color = color;
120            this.descent = (int) metrics.getDescent();
121            this.ascent = (int) metrics.getAscent();
122            this.feature = feature;
123            this.halo = halo;
124            this.rotation = rotation;
125            this.anchorPoint = anchorPoint;
126    
127            this.w = w;
128            this.h = h;
129    
130            this.opacity = opacity;
131    
132            int dx = (int) ( -anchorPoint[0] * w + displacement[0] + 0.5 );
133            int dy = (int) ( anchorPoint[1] * h - displacement[1] + 0.5 );
134            // vertices of label boundary
135            int[] xpoints = new int[4];
136            int[] ypoints = new int[4];
137            xpoints[0] = x + dx;
138            ypoints[0] = y + dy;
139            xpoints[1] = x + w + dx;
140            ypoints[1] = y + dy;
141            xpoints[2] = x + w + dx;
142            ypoints[2] = y - h + dy;
143            xpoints[3] = x + dx;
144            ypoints[3] = y - h + dy;
145    
146            // get rotated + translated points
147            this.xpoints = new int[4];
148            this.ypoints = new int[4];
149            int tx = xpoints[0];
150            int ty = ypoints[0];
151    
152            // transform all vertices of the boundary
153            for ( int i = 0; i < 4; i++ ) {
154                int[] point = transformPoint( xpoints[i], ypoints[i], tx, ty, rotation );
155                this.xpoints[i] = point[0];
156                this.ypoints[i] = point[1];
157            }
158    
159            this.opacity = opacity;
160        }
161    
162        /**
163         * 
164         * @return caption
165         */
166        public String getCaption() {
167            return caption;
168        }
169    
170        /**
171         * 
172         * @return rotation
173         */
174        public double getRotation() {
175            return rotation;
176        }
177    
178        /**
179         *
180         */
181        public void paintBoundaries( Graphics2D g ) {
182            setColor( g, new Color( 0x888888 ), 0.5 );
183            g.fillPolygon( xpoints, ypoints, xpoints.length );
184            g.setColor( Color.BLACK );
185    
186            // get the current transform
187            AffineTransform saveAT = g.getTransform();
188    
189            // translation parameters (rotation)
190            AffineTransform transform = new AffineTransform();
191    
192            // render the text
193            transform.rotate( rotation / 180d * Math.PI, xpoints[0], ypoints[0] );
194            g.setTransform( transform );
195            // g.drawString( caption, xpoints [0], ypoints [0] - descent);
196    
197            // restore original transform
198            g.setTransform( saveAT );
199        }
200    
201        /**
202         * Renders the label (including halo) to the submitted <tt>Graphics2D</tt> context.
203         * <p>
204         * 
205         * @param g
206         *            <tt>Graphics2D</tt> context to be used
207         */
208        public void paint( Graphics2D g ) {
209    
210            // get the current transform
211            AffineTransform saveAT = g.getTransform();
212    
213            // perform transformation
214            AffineTransform transform = new AffineTransform();
215    
216            transform.rotate( rotation / 180d * Math.PI, xpoints[0], ypoints[0] );
217            g.setTransform( transform );
218    
219            // render the halo (only if specified)
220            if ( halo != null ) {
221                try {
222                    paintHalo( g, halo, (int) ( xpoints[0] - w * anchorPoint[0] ),
223                               (int) ( ypoints[0] - descent + h * anchorPoint[1] ) );
224                } catch ( FilterEvaluationException e ) {
225                    e.printStackTrace();
226                }
227            }
228    
229            // render the text
230            setColor( g, color, opacity );
231            g.setFont( font );
232            g.drawString( caption, (int) ( xpoints[0] - w * anchorPoint[0] ),
233                          (int) ( ypoints[0] - descent + h * anchorPoint[1] ) );
234    
235            // restore original transform
236            g.setTransform( saveAT );
237        }
238    
239        /**
240         * Renders the label's halo to the submitted <tt>Graphics2D</tt> context.
241         * <p>
242         * 
243         * @param g
244         *            <tt>Graphics2D</tt> context to be used
245         * @param halo
246         *            <tt>Halo</tt> from the SLD
247         * @param x
248         *            x-coordinate of the label
249         * @param y
250         *            y-coordinate of the label
251         * 
252         * @throws FilterEvaluationException
253         *             if the evaluation of a <tt>ParameterValueType</tt> fails
254         */
255        private void paintHalo( Graphics2D g, Halo halo, int x, int y )
256                                throws FilterEvaluationException {
257    
258            int radius = (int) halo.getRadius( feature );
259    
260            // only draw filled rectangle or circle, if Fill-Element is given
261            Fill fill = halo.getFill();
262    
263            if ( fill != null ) {
264                GraphicFill gFill = fill.getGraphicFill();
265    
266                if ( gFill != null ) {
267                    BufferedImage texture = gFill.getGraphic().getAsImage( feature );
268                    Rectangle anchor = new Rectangle( 0, 0, texture.getWidth( null ), texture.getHeight( null ) );
269                    g.setPaint( new TexturePaint( texture, anchor ) );
270                } else {
271                    double opacity = fill.getOpacity( feature );
272                    Color color = fill.getFill( feature );
273                    setColor( g, color, opacity );
274                }
275            } else {
276                g.setColor( Color.white );
277            }
278    
279            // radius specified -> draw circle
280            if ( radius > 0 ) {
281                g.fillOval( ( x + ( w >> 1 ) ) - radius, y - ( ascent >> 1 ) - radius, radius << 1, radius << 1 );
282            }
283            // radius unspecified -> draw rectangle
284            else {
285                g.fillRect( x - 1, y - ascent - 1, w + 2, h + 2 );
286            }
287    
288            // only stroke outline, if Stroke-Element is given
289            org.deegree.graphics.sld.Stroke stroke = halo.getStroke();
290    
291            if ( stroke != null ) {
292                double opacity = stroke.getOpacity( feature );
293    
294                if ( opacity > 0.01 ) {
295                    Color color = stroke.getStroke( feature );
296                    int alpha = (int) Math.round( opacity * 255 );
297                    int red = color.getRed();
298                    int green = color.getGreen();
299                    int blue = color.getBlue();
300                    color = new Color( red, green, blue, alpha );
301                    g.setColor( color );
302    
303                    float[] dash = stroke.getDashArray( feature );
304    
305                    // use a simple Stroke if dash == null or dash length < 2
306                    BasicStroke bs = null;
307                    float strokeWidth = (float) stroke.getWidth( feature );
308    
309                    if ( ( dash == null ) || ( dash.length < 2 ) ) {
310                        bs = new BasicStroke( strokeWidth );
311                    } else {
312                        bs = new BasicStroke( strokeWidth, stroke.getLineCap( feature ), stroke.getLineJoin( feature ),
313                                              10.0f, dash, stroke.getDashOffset( feature ) );
314                        bs = new BasicStroke( strokeWidth, stroke.getLineCap( feature ), stroke.getLineJoin( feature ),
315                                              1.0f, dash, 1.0f );
316                    }
317    
318                    g.setStroke( bs );
319    
320                    // radius specified -> draw circle
321                    if ( radius > 0 ) {
322                        g.drawOval( ( x + ( w >> 1 ) ) - radius, y - ( ascent >> 1 ) - radius, radius << 1, radius << 1 );
323                    }// radius unspecified -> draw rectangle
324                    else {
325                        g.drawRect( x - 1, y - ascent - 1, w + 2, h + 2 );
326                    }
327                }
328            }
329        }
330    
331        public int getX() {
332            return xpoints[0];
333        }
334    
335        public int getY() {
336            return ypoints[0];
337        }
338    
339        public int getMaxX() {
340            return xpoints[1];
341        }
342    
343        public int getMaxY() {
344            return ypoints[1];
345        }
346    
347        public int getMinX() {
348            return xpoints[3];
349        }
350    
351        public int getMinY() {
352            return ypoints[3];
353        }
354    
355        /**
356         * Determines if the label intersects with another label.
357         * <p>
358         * 
359         * @param that
360         *            label to test
361         * @return true if the labels intersect
362         */
363        public boolean intersects( Label that ) {
364            LOG.logInfo( "Intersection test for rotated labels is not implemented yet!" );
365            return false;
366        }
367    
368        private int[] transformPoint( int x, int y, int tx, int ty, double rotation ) {
369    
370            double cos = Math.cos( rotation );
371            double sin = Math.sin( rotation );
372    
373            double m00 = cos;
374            double m01 = -sin;
375            // double m02 = cos * dx - sin * dy + tx - tx * cos + ty * sin;
376            double m02 = tx - tx * cos + ty * sin;
377            double m10 = sin;
378            double m11 = cos;
379            // double m12 = sin * dx + cos * dy + ty - tx * sin - ty * cos;
380            double m12 = ty - tx * sin - ty * cos;
381    
382            int[] point2 = new int[2];
383    
384            point2[0] = (int) ( m00 * x + m01 * y + m02 + 0.5 );
385            point2[1] = (int) ( m10 * x + m11 * y + m12 + 0.5 );
386    
387            return point2;
388        }
389    
390        private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
391            if ( opacity < 0.999 ) {
392                final int alpha = (int) Math.round( opacity * 255 );
393                final int red = color.getRed();
394                final int green = color.getGreen();
395                final int blue = color.getBlue();
396                color = new Color( red, green, blue, alpha );
397            }
398    
399            g2.setColor( color );
400            return g2;
401        }
402    
403        @Override
404        public String toString() {
405            return caption;
406        }
407    }