001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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: 21452 $, $Date: 2009-12-15 15:08:09 +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 }