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    }