001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/graphics/displayelements/PolygonDisplayElement.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.Graphics; 041 import java.awt.Graphics2D; 042 import java.awt.Image; 043 import java.awt.Rectangle; 044 import java.awt.TexturePaint; 045 import java.awt.geom.AffineTransform; 046 import java.awt.geom.GeneralPath; 047 import java.awt.image.BufferedImage; 048 import java.io.Serializable; 049 import java.util.ArrayList; 050 import java.util.Iterator; 051 import java.util.List; 052 053 import org.deegree.framework.log.ILogger; 054 import org.deegree.framework.log.LoggerFactory; 055 import org.deegree.graphics.sld.GraphicFill; 056 import org.deegree.graphics.sld.PolygonSymbolizer; 057 import org.deegree.graphics.sld.Symbolizer; 058 import org.deegree.graphics.transformation.GeoTransform; 059 import org.deegree.model.feature.Feature; 060 import org.deegree.model.filterencoding.FilterEvaluationException; 061 import org.deegree.model.spatialschema.Envelope; 062 import org.deegree.model.spatialschema.Geometry; 063 import org.deegree.model.spatialschema.GeometryFactory; 064 import org.deegree.model.spatialschema.MultiPrimitive; 065 import org.deegree.model.spatialschema.MultiSurface; 066 import org.deegree.model.spatialschema.Position; 067 import org.deegree.model.spatialschema.Primitive; 068 import org.deegree.model.spatialschema.Surface; 069 import org.deegree.model.spatialschema.SurfacePatch; 070 071 /** 072 * {@link DisplayElement} that encapsulates a {@link Surface} or {@link MultiSurface} geometry and a 073 * {@link PolygonSymbolizer}. 074 * 075 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 076 * @author last edited by: $Author: aschmitz $ 077 * 078 * @version $Revision: 21456 $, $Date: 2009-12-15 15:17:33 +0100 (Di, 15 Dez 2009) $ 079 */ 080 public class PolygonDisplayElement extends GeometryDisplayElement implements DisplayElement, Serializable { 081 082 private static final ILogger LOG = LoggerFactory.getLogger( PolygonDisplayElement.class ); 083 084 /** Use serialVersionUID for interoperability. */ 085 private final static long serialVersionUID = -2980154437699081214L; 086 087 private List<int[][]> pathes = new ArrayList<int[][]>( 1000 ); 088 089 private static Color transparency = new Color( 0, 0, 0, 0f ); 090 091 /** 092 * Creates a new PolygonDisplayElement object. 093 * 094 * @param feature 095 * @param geometry 096 */ 097 public PolygonDisplayElement( Feature feature, Surface geometry ) { 098 super( feature, geometry, null ); 099 100 Symbolizer defaultSymbolizer = new PolygonSymbolizer(); 101 this.setSymbolizer( defaultSymbolizer ); 102 } 103 104 /** 105 * Creates a new PolygonDisplayElement object. 106 * 107 * @param feature 108 * @param geometry 109 * @param symbolizer 110 */ 111 public PolygonDisplayElement( Feature feature, Surface geometry, PolygonSymbolizer symbolizer ) { 112 super( feature, geometry, symbolizer ); 113 } 114 115 /** 116 * Creates a new PolygonDisplayElement object. 117 * 118 * @param feature 119 * @param geometry 120 */ 121 public PolygonDisplayElement( Feature feature, MultiSurface geometry ) { 122 super( feature, geometry, null ); 123 124 Symbolizer defaultSymbolizer = new PolygonSymbolizer(); 125 this.setSymbolizer( defaultSymbolizer ); 126 } 127 128 /** 129 * Creates a new PolygonDisplayElement object. 130 * 131 * @param feature 132 * @param geometry 133 * @param symbolizer 134 */ 135 public PolygonDisplayElement( Feature feature, MultiSurface geometry, PolygonSymbolizer symbolizer ) { 136 super( feature, geometry, symbolizer ); 137 } 138 139 /** 140 * renders the DisplayElement to the submitted graphic context 141 * 142 * @param g 143 * @param projection 144 * @param scale 145 */ 146 public void paint( Graphics g, GeoTransform projection, double scale ) { 147 synchronized ( symbolizer ) { 148 if ( feature != null ) { 149 ( (ScaledFeature) feature ).setScale( scale ); 150 } 151 try { 152 // a local instance must be used because following statement may 153 // changes the original geometry 154 Geometry geom = geometry; 155 if ( geom == null ) { 156 LOG.logInfo( "null geometry in " + this.getClass().getName() ); 157 return; 158 } 159 Envelope env = growEnvelope( projection.getSourceRect(), 0.05f ); 160 Surface tmp = GeometryFactory.createSurface( env, geom.getCoordinateSystem() ); 161 162 if ( !geom.intersects( tmp ) ) { 163 return; 164 } 165 166 if ( geom instanceof Surface ) { 167 try { 168 Geometry gg = geom.intersection( tmp ); 169 if ( gg != null ) { 170 geom = gg; 171 } 172 } catch ( Exception e ) { 173 LOG.logWarning( e.getMessage() ); 174 } 175 if ( geom instanceof Surface ) { 176 GeneralPath path = calcPolygonPath( projection, (Surface) geom ); 177 if ( path != null ) { 178 drawPolygon( g, path ); 179 } else { 180 LOG.logWarning( "null path in " + this.getClass().getName() ); 181 } 182 } else { 183 MultiPrimitive msurface = (MultiPrimitive) geom; 184 drawMultiSurface( g, projection, tmp, msurface ); 185 } 186 } else { 187 MultiPrimitive msurface = (MultiPrimitive) geom; 188 drawMultiSurface( g, projection, tmp, msurface ); 189 } 190 } catch ( FilterEvaluationException e ) { 191 LOG.logError( "FilterEvaluationException caught evaluating an Expression!", e ); 192 } catch ( Exception ex ) { 193 LOG.logError( "Exception caught evaluating an Expression!", ex ); 194 } 195 } 196 this.pathes.clear(); 197 } 198 199 private void drawMultiSurface( Graphics g, GeoTransform projection, Surface tmp, MultiPrimitive msurface ) 200 throws Exception, FilterEvaluationException { 201 for ( int i = 0; i < msurface.getSize(); i++ ) { 202 Primitive prim = msurface.getPrimitiveAt( i ); 203 try { 204 Geometry gg = prim.intersection( tmp ); 205 if ( gg != null ) { 206 prim = (Primitive) gg; 207 } 208 } catch ( Exception e ) { 209 LOG.logWarning( e.getMessage() ); 210 } 211 if ( prim instanceof Surface ) { 212 213 GeneralPath path = calcPolygonPath( projection, (Surface) prim ); 214 if ( path != null ) { 215 drawPolygon( g, path ); 216 } else { 217 LOG.logWarning( "null path in " + this.getClass().getName() ); 218 } 219 } else { 220 LOG.logWarning( getClass().getName() + ": " + prim.getClass().getName() ); 221 } 222 } 223 } 224 225 private double distance( Position p1, Position p2 ) { 226 double x1 = p1.getX(); 227 double y1 = p1.getY(); 228 double x2 = p2.getX(); 229 double y2 = p2.getY(); 230 return Math.sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) ); 231 } 232 233 private GeneralPath calcPolygonPath( GeoTransform projection, Surface surface ) 234 throws Exception { 235 GeneralPath path = new GeneralPath(); 236 237 SurfacePatch patch = surface.getSurfacePatchAt( 0 ); 238 if ( patch == null ) 239 return null; 240 appendRingToPath( path, patch.getExteriorRing(), projection ); 241 Position[][] inner = patch.getInteriorRings(); 242 if ( inner != null ) { 243 for ( int i = 0; i < inner.length; i++ ) { 244 appendRingToPath( path, inner[i], projection ); 245 } 246 } 247 248 return path; 249 } 250 251 private void appendRingToPath( GeneralPath path, Position[] ring, GeoTransform projection ) { 252 if ( ring.length == 0 ) 253 return; 254 255 int[] x = new int[ring.length]; 256 int[] y = new int[ring.length]; 257 int k = 0; 258 259 Position p = projection.getDestPoint( ring[0] ); 260 Position pp = p; 261 path.moveTo( (float) p.getX(), (float) p.getY() ); 262 for ( int i = 1; i < ring.length; i++ ) { 263 p = projection.getDestPoint( ring[i] ); 264 if ( distance( p, pp ) > 1 ) { 265 path.lineTo( (float) p.getX(), (float) p.getY() ); 266 pp = p; 267 x[k] = (int) p.getX(); 268 y[k++] = (int) p.getY(); 269 } 270 } 271 path.closePath(); 272 int[][] tmp = new int[3][]; 273 tmp[0] = x; 274 tmp[1] = y; 275 tmp[2] = new int[] { k }; 276 pathes.add( tmp ); 277 } 278 279 private void drawPolygon( Graphics g, GeneralPath path ) 280 throws FilterEvaluationException { 281 Graphics2D g2 = (Graphics2D) g; 282 283 PolygonSymbolizer sym = (PolygonSymbolizer) symbolizer; 284 org.deegree.graphics.sld.Fill fill = sym.getFill(); 285 org.deegree.graphics.sld.Stroke stroke = sym.getStroke(); 286 287 if ( fill != null ) { 288 double opacity = fill.getOpacity( feature ); 289 290 // is completely transparent 291 // if not fill polygon 292 if ( opacity > 0.01 ) { 293 Color color = fill.getFill( feature ); 294 int alpha = (int) Math.round( opacity * 255 ); 295 int red = color.getRed(); 296 int green = color.getGreen(); 297 int blue = color.getBlue(); 298 color = new Color( red, green, blue, alpha ); 299 300 GraphicFill gFill = fill.getGraphicFill(); 301 302 if ( gFill != null ) { 303 BufferedImage texture = gFill.getGraphic().getAsImage( feature ); 304 for ( int i = 0; i < texture.getWidth(); i++ ) { 305 for ( int j = 0; j < texture.getHeight(); j++ ) { 306 if ( texture.getRGB( i, j ) == Color.BLACK.getRGB() ) { 307 texture.setRGB( i, j, color.getRGB() ); 308 } 309 } 310 } 311 if ( texture != null ) { 312 Rectangle anchor = new Rectangle( 0, 0, texture.getWidth(), texture.getHeight() ); 313 g2.setColor( transparency ); 314 g2.setPaint( new TexturePaint( texture, anchor ) ); 315 } else { 316 g2.setColor( color ); 317 } 318 } else { 319 g2.setColor( color ); 320 } 321 try { 322 g2.fill( path ); 323 } catch ( Exception e ) { 324 // why are all exceptions catched here? 325 } 326 } 327 } 328 329 // only stroke outline, if Stroke-Element is given 330 if ( stroke != null ) { 331 if ( stroke.getOpacity( feature ) > 0.001 ) { 332 // do not paint if feature is completly transparent 333 drawLine( g2, path, stroke ); 334 } 335 if ( stroke.getGraphicStroke() != null ) { 336 try { 337 Image image = stroke.getGraphicStroke().getGraphic().getAsImage( feature ); 338 CurveWalker walker = new CurveWalker( g.getClipBounds() ); 339 340 int[][] pos = null; 341 for ( int i = 0; i < pathes.size(); i++ ) { 342 pos = pathes.get( i ); 343 ArrayList<double[]> positions = walker.createPositions( pos, image.getWidth( null ), true ); 344 Iterator<double[]> it = positions.iterator(); 345 while ( it.hasNext() ) { 346 double[] label = it.next(); 347 int x = (int) ( label[0] + 0.5 ); 348 int y = (int) ( label[1] + 0.5 ); 349 paintImage( image, g2, x, y, Math.toRadians( label[2] ) ); 350 } 351 } 352 } catch ( Exception e ) { 353 LOG.logError( e.getMessage(), e ); 354 } 355 } 356 357 } 358 pathes.clear(); 359 } 360 361 /** 362 * Renders a curve to the submitted graphic context. 363 * 364 * TODO: Calculate miterlimit. 365 */ 366 private void drawLine( Graphics g, GeneralPath path, org.deegree.graphics.sld.Stroke stroke ) 367 throws FilterEvaluationException { 368 369 // Color & Opacity 370 Graphics2D g2 = (Graphics2D) g; 371 setColor( g2, stroke.getStroke( feature ), stroke.getOpacity( feature ) ); 372 373 float[] dash = stroke.getDashArray( feature ); 374 375 // use a simple Stroke if dash == null or its length < 2 376 // that's faster 377 float width = (float) stroke.getWidth( feature ); 378 int cap = stroke.getLineCap( feature ); 379 int join = stroke.getLineJoin( feature ); 380 BasicStroke bs2 = null; 381 382 if ( ( dash == null ) || ( dash.length < 2 ) ) { 383 bs2 = new BasicStroke( width, cap, join ); 384 } else { 385 bs2 = new BasicStroke( width, cap, join, 10.0f, dash, stroke.getDashOffset( feature ) ); 386 } 387 388 g2.setStroke( bs2 ); 389 g2.draw( path ); 390 391 } 392 393 /** 394 * 395 * 396 * @param g2 397 * @param color 398 * @param opacity 399 * 400 * @return the graphics object 401 */ 402 private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) { 403 if ( opacity < 0.999 ) { 404 // just use a color having an alpha channel if a significant 405 // level of transparency has been defined 406 final int alpha = (int) Math.round( opacity * 255 ); 407 final int red = color.getRed(); 408 final int green = color.getGreen(); 409 final int blue = color.getBlue(); 410 color = new Color( red, green, blue, alpha ); 411 } 412 413 g2.setColor( color ); 414 return g2; 415 } 416 417 /** 418 * 419 * @param image 420 * @param g 421 * @param x 422 * @param y 423 * @param rotation 424 */ 425 private void paintImage( Image image, Graphics2D g, int x, int y, double rotation ) { 426 427 // get the current transform 428 AffineTransform saveAT = g.getTransform(); 429 430 // translation parameters (rotation) 431 AffineTransform transform = new AffineTransform(); 432 transform.rotate( rotation, x, y ); 433 transform.translate( -image.getWidth( null ), -image.getHeight( null ) / 2.0 ); 434 g.setTransform( transform ); 435 436 // render the image 437 g.drawImage( image, x, y, null ); 438 439 // restore original transform 440 g.setTransform( saveAT ); 441 } 442 }