001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/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: 27114 $ $Date: 2010-09-30 16:04:40 +0200 (Do, 30 Sep 2010) $
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        RotatedLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, Halo halo, int x,
099                      int y, int w, int h, double rotation, double anchorPoint[], double[] displacement, double opacity ) {
100            this( caption, font, color, metrics, feature, halo, x, y, w, h, rotation, anchorPoint, opacity );
101    
102            int dx = (int) ( -anchorPoint[0] * w + displacement[0] + 0.5 );
103            int dy = (int) ( anchorPoint[1] * h - displacement[1] + 0.5 );
104            // vertices of label boundary
105            int[] xpoints = new int[4];
106            int[] ypoints = new int[4];
107            xpoints[0] = x + dx;
108            ypoints[0] = y + dy;
109            xpoints[1] = x + w + dx;
110            ypoints[1] = y + dy;
111            xpoints[2] = x + w + dx;
112            ypoints[2] = y - h + dy;
113            xpoints[3] = x + dx;
114            ypoints[3] = y - h + dy;
115    
116            // get rotated + translated points
117            this.xpoints = new int[4];
118            this.ypoints = new int[4];
119            int tx = xpoints[0];
120            int ty = ypoints[0];
121    
122            // transform all vertices of the boundary
123            for ( int i = 0; i < 4; i++ ) {
124                int[] point = transformPoint( xpoints[i], ypoints[i], tx, ty, rotation );
125                this.xpoints[i] = point[0];
126                this.ypoints[i] = point[1];
127            }
128    
129            this.opacity = opacity;
130        }
131    
132        /**
133         * 
134         * @param caption
135         * @param font
136         * @param color
137         * @param metrics
138         * @param feature
139         * @param halo
140         * @param x
141         * @param y
142         * @param w
143         * @param h
144         * @param rotation
145         * @param anchorPoint
146         * @param opacity
147         * @Deprecated use
148         *             {@link #RotatedLabel(String, Font, Color, LineMetrics, Feature, Halo, int, int, int, int, double, double[], double[], double)}
149         *             instead
150         */
151        RotatedLabel( String caption, Font font, Color color, LineMetrics metrics, Feature feature, Halo halo, int x,
152                      int y, int w, int h, double rotation, double anchorPoint[], double opacity ) {
153            this.caption = caption;
154            this.font = font;
155            this.color = color;
156            this.descent = (int) metrics.getDescent();
157            this.ascent = (int) metrics.getAscent();
158            this.feature = feature;
159            this.halo = halo;
160            this.rotation = rotation;
161            this.anchorPoint = anchorPoint;
162    
163            this.w = w;
164            this.h = h;
165    
166            // vertices of label boundary
167            int[] xpoints = new int[4];
168            int[] ypoints = new int[4];
169            xpoints[0] = x;
170            ypoints[0] = y;
171            xpoints[1] = x + w;
172            ypoints[1] = y;
173            xpoints[2] = x + w;
174            ypoints[2] = y - h;
175            xpoints[3] = x;
176            ypoints[3] = y - h;
177    
178            // get rotated + translated points
179            this.xpoints = new int[4];
180            this.ypoints = new int[4];
181            int tx = xpoints[0];
182            int ty = ypoints[0];
183    
184            // transform all vertices of the boundary
185            for ( int i = 0; i < 4; i++ ) {
186                int[] point = transformPoint( xpoints[i], ypoints[i], tx, ty, rotation );
187                this.xpoints[i] = point[0];
188                this.ypoints[i] = point[1];
189            }
190    
191            this.opacity = opacity;
192        }
193    
194        /**
195         * 
196         * @return caption
197         */
198        public String getCaption() {
199            return caption;
200        }
201    
202        /**
203         * 
204         * @return rotation
205         */
206        public double getRotation() {
207            return rotation;
208        }
209    
210        /**
211         *
212         */
213        public void paintBoundaries( Graphics2D g ) {
214            setColor( g, new Color( 0x888888 ), 0.5 );
215            g.fillPolygon( xpoints, ypoints, xpoints.length );
216            g.setColor( Color.BLACK );
217    
218            // get the current transform
219            AffineTransform saveAT = g.getTransform();
220    
221            // translation parameters (rotation)
222            AffineTransform transform = new AffineTransform();
223    
224            // render the text
225            transform.rotate( rotation / 180d * Math.PI, xpoints[0], ypoints[0] );
226            g.setTransform( transform );
227            // g.drawString( caption, xpoints [0], ypoints [0] - descent);
228    
229            // restore original transform
230            g.setTransform( saveAT );
231        }
232    
233        /**
234         * Renders the label (including halo) to the submitted <tt>Graphics2D</tt> context.
235         * <p>
236         * 
237         * @param g
238         *            <tt>Graphics2D</tt> context to be used
239         */
240        public void paint( Graphics2D g ) {
241    
242            // get the current transform
243            AffineTransform saveAT = g.getTransform();
244    
245            // perform transformation
246            AffineTransform transform = new AffineTransform();
247    
248            transform.rotate( rotation / 180d * Math.PI, xpoints[0], ypoints[0] );
249            g.setTransform( transform );
250    
251            // render the halo (only if specified)
252            if ( halo != null ) {
253                try {
254                    paintHalo( g, halo, (int) ( xpoints[0] - w * anchorPoint[0] ),
255                               (int) ( ypoints[0] - descent + h * anchorPoint[1] ) );
256                } catch ( FilterEvaluationException e ) {
257                    e.printStackTrace();
258                }
259            }
260    
261            // render the text
262            setColor( g, color, opacity );
263            g.setFont( font );
264            g.drawString( caption, (int) ( xpoints[0] - w * anchorPoint[0] ),
265                          (int) ( ypoints[0] - descent + h * anchorPoint[1] ) );
266    
267            // restore original transform
268            g.setTransform( saveAT );
269        }
270    
271        /**
272         * Renders the label's halo to the submitted <tt>Graphics2D</tt> context.
273         * <p>
274         * 
275         * @param g
276         *            <tt>Graphics2D</tt> context to be used
277         * @param halo
278         *            <tt>Halo</tt> from the SLD
279         * @param x
280         *            x-coordinate of the label
281         * @param y
282         *            y-coordinate of the label
283         * 
284         * @throws FilterEvaluationException
285         *             if the evaluation of a <tt>ParameterValueType</tt> fails
286         */
287        private void paintHalo( Graphics2D g, Halo halo, int x, int y )
288                                throws FilterEvaluationException {
289    
290            int radius = (int) halo.getRadius( feature );
291    
292            // only draw filled rectangle or circle, if Fill-Element is given
293            Fill fill = halo.getFill();
294    
295            if ( fill != null ) {
296                GraphicFill gFill = fill.getGraphicFill();
297    
298                if ( gFill != null ) {
299                    BufferedImage texture = gFill.getGraphic().getAsImage( feature );
300                    Rectangle anchor = new Rectangle( 0, 0, texture.getWidth( null ), texture.getHeight( null ) );
301                    g.setPaint( new TexturePaint( texture, anchor ) );
302                } else {
303                    double opacity = fill.getOpacity( feature );
304                    Color color = fill.getFill( feature );
305                    setColor( g, color, opacity );
306                }
307            } else {
308                g.setColor( Color.white );
309            }
310    
311            // radius specified -> draw circle
312            if ( radius > 0 ) {
313                g.fillOval( ( x + ( w >> 1 ) ) - radius, y - ( ascent >> 1 ) - radius, radius << 1, radius << 1 );
314            }
315            // radius unspecified -> draw rectangle
316            else {
317                g.fillRect( x - 1, y - ascent - 1, w + 2, h + 2 );
318            }
319    
320            // only stroke outline, if Stroke-Element is given
321            org.deegree.graphics.sld.Stroke stroke = halo.getStroke();
322    
323            if ( stroke != null ) {
324                double opacity = stroke.getOpacity( feature );
325    
326                if ( opacity > 0.01 ) {
327                    Color color = stroke.getStroke( feature );
328                    int alpha = (int) Math.round( opacity * 255 );
329                    int red = color.getRed();
330                    int green = color.getGreen();
331                    int blue = color.getBlue();
332                    color = new Color( red, green, blue, alpha );
333                    g.setColor( color );
334    
335                    float[] dash = stroke.getDashArray( feature );
336    
337                    // use a simple Stroke if dash == null or dash length < 2
338                    BasicStroke bs = null;
339                    float strokeWidth = (float) stroke.getWidth( feature );
340    
341                    if ( ( dash == null ) || ( dash.length < 2 ) ) {
342                        bs = new BasicStroke( strokeWidth );
343                    } else {
344                        bs = new BasicStroke( strokeWidth, stroke.getLineCap( feature ), stroke.getLineJoin( feature ),
345                                              10.0f, dash, stroke.getDashOffset( feature ) );
346                        bs = new BasicStroke( strokeWidth, stroke.getLineCap( feature ), stroke.getLineJoin( feature ),
347                                              1.0f, dash, 1.0f );
348                    }
349    
350                    g.setStroke( bs );
351    
352                    // radius specified -> draw circle
353                    if ( radius > 0 ) {
354                        g.drawOval( ( x + ( w >> 1 ) ) - radius, y - ( ascent >> 1 ) - radius, radius << 1, radius << 1 );
355                    }// radius unspecified -> draw rectangle
356                    else {
357                        g.drawRect( x - 1, y - ascent - 1, w + 2, h + 2 );
358                    }
359                }
360            }
361        }
362    
363        public int getX() {
364            return xpoints[0];
365        }
366    
367        public int getY() {
368            return ypoints[0];
369        }
370    
371        public int getMaxX() {
372            return xpoints[1];
373        }
374    
375        public int getMaxY() {
376            return ypoints[1];
377        }
378    
379        public int getMinX() {
380            return xpoints[3];
381        }
382    
383        public int getMinY() {
384            return ypoints[3];
385        }
386    
387        /**
388         * Determines if the label intersects with another label.
389         * <p>
390         * 
391         * @param that
392         *            label to test
393         * @return true if the labels intersect
394         */
395        public boolean intersects( Label that ) {
396            LOG.logInfo( "Intersection test for rotated labels is not implemented yet!" );
397            return false;
398        }
399    
400        private int[] transformPoint( int x, int y, int tx, int ty, double rotation ) {
401    
402            double cos = Math.cos( rotation );
403            double sin = Math.sin( rotation );
404    
405            double m00 = cos;
406            double m01 = -sin;
407            // double m02 = cos * dx - sin * dy + tx - tx * cos + ty * sin;
408            double m02 = tx - tx * cos + ty * sin;
409            double m10 = sin;
410            double m11 = cos;
411            // double m12 = sin * dx + cos * dy + ty - tx * sin - ty * cos;
412            double m12 = ty - tx * sin - ty * cos;
413    
414            int[] point2 = new int[2];
415    
416            point2[0] = (int) ( m00 * x + m01 * y + m02 + 0.5 );
417            point2[1] = (int) ( m10 * x + m11 * y + m12 + 0.5 );
418    
419            return point2;
420        }
421    
422        private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
423            if ( opacity < 0.999 ) {
424                final int alpha = (int) Math.round( opacity * 255 );
425                final int red = color.getRed();
426                final int green = color.getGreen();
427                final int blue = color.getBlue();
428                color = new Color( red, green, blue, alpha );
429            }
430    
431            g2.setColor( color );
432            return g2;
433        }
434    
435        @Override
436        public String toString() {
437            return caption;
438        }
439    }