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 }