001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/displayelements/PolygonDisplayElement.java $
002    /*----------------    FILE HEADER  -------------/-----------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2008 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53177 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     
043     ---------------------------------------------------------------------------*/
044    package org.deegree.graphics.displayelements;
045    
046    import java.awt.BasicStroke;
047    import java.awt.Color;
048    import java.awt.Graphics;
049    import java.awt.Graphics2D;
050    import java.awt.Image;
051    import java.awt.Rectangle;
052    import java.awt.TexturePaint;
053    import java.awt.geom.AffineTransform;
054    import java.awt.geom.GeneralPath;
055    import java.awt.image.BufferedImage;
056    import java.io.Serializable;
057    import java.util.ArrayList;
058    import java.util.Iterator;
059    import java.util.List;
060    
061    import org.deegree.framework.log.ILogger;
062    import org.deegree.framework.log.LoggerFactory;
063    import org.deegree.graphics.sld.GraphicFill;
064    import org.deegree.graphics.sld.PolygonSymbolizer;
065    import org.deegree.graphics.sld.Symbolizer;
066    import org.deegree.graphics.transformation.GeoTransform;
067    import org.deegree.model.feature.Feature;
068    import org.deegree.model.filterencoding.FilterEvaluationException;
069    import org.deegree.model.spatialschema.Envelope;
070    import org.deegree.model.spatialschema.Geometry;
071    import org.deegree.model.spatialschema.GeometryFactory;
072    import org.deegree.model.spatialschema.MultiPrimitive;
073    import org.deegree.model.spatialschema.MultiSurface;
074    import org.deegree.model.spatialschema.Position;
075    import org.deegree.model.spatialschema.Primitive;
076    import org.deegree.model.spatialschema.Surface;
077    import org.deegree.model.spatialschema.SurfacePatch;
078    
079    /**
080     * {@link DisplayElement} that encapsulates a {@link Surface} or {@link MultiSurface} geometry and a
081     * {@link PolygonSymbolizer}.
082     * 
083     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
084     * @author last edited by: $Author: aschmitz $
085     * 
086     * @version $Revision: 12483 $, $Date: 2008-06-23 13:43:29 +0200 (Mo, 23 Jun 2008) $
087     */
088    public class PolygonDisplayElement extends GeometryDisplayElement implements DisplayElement, Serializable {
089    
090        private static final ILogger LOG = LoggerFactory.getLogger( PolygonDisplayElement.class );
091    
092        /** Use serialVersionUID for interoperability. */
093        private final static long serialVersionUID = -2980154437699081214L;
094    
095        private List<int[][]> pathes = new ArrayList<int[][]>( 1000 );
096    
097        /**
098         * Creates a new PolygonDisplayElement object.
099         * 
100         * @param feature
101         * @param geometry
102         */
103        public PolygonDisplayElement( Feature feature, Surface geometry ) {
104            super( feature, geometry, null );
105    
106            Symbolizer defaultSymbolizer = new PolygonSymbolizer();
107            this.setSymbolizer( defaultSymbolizer );
108        }
109    
110        /**
111         * Creates a new PolygonDisplayElement object.
112         * 
113         * @param feature
114         * @param geometry
115         * @param symbolizer
116         */
117        public PolygonDisplayElement( Feature feature, Surface geometry, PolygonSymbolizer symbolizer ) {
118            super( feature, geometry, symbolizer );
119        }
120    
121        /**
122         * Creates a new PolygonDisplayElement object.
123         * 
124         * @param feature
125         * @param geometry
126         */
127        public PolygonDisplayElement( Feature feature, MultiSurface geometry ) {
128            super( feature, geometry, null );
129    
130            Symbolizer defaultSymbolizer = new PolygonSymbolizer();
131            this.setSymbolizer( defaultSymbolizer );
132        }
133    
134        /**
135         * Creates a new PolygonDisplayElement object.
136         * 
137         * @param feature
138         * @param geometry
139         * @param symbolizer
140         */
141        public PolygonDisplayElement( Feature feature, MultiSurface geometry, PolygonSymbolizer symbolizer ) {
142            super( feature, geometry, symbolizer );
143        }
144    
145        /**
146         * renders the DisplayElement to the submitted graphic context
147         * 
148         * @param g
149         * @param projection
150         * @param scale
151         */
152        public void paint( Graphics g, GeoTransform projection, double scale ) {
153            synchronized ( symbolizer ) {
154                if ( feature != null ) {
155                    ( (ScaledFeature) feature ).setScale( scale );
156                }
157                try {
158                    // a local instance must be used because following statement may
159                    // changes the original geometry
160                    Geometry geom = geometry;
161                    if ( geom == null ) {
162                        LOG.logInfo( "null geometry in " + this.getClass().getName() );
163                        return;
164                    }
165                    Envelope env = growEnvelope( projection.getSourceRect(), 0.05f );
166                    Surface tmp = GeometryFactory.createSurface( env, geom.getCoordinateSystem() );
167    
168                    if ( !geom.intersects( tmp ) ) {
169                        return;
170                    }
171                    geom = geom.intersection( tmp );
172    
173                    if ( geom instanceof Surface ) {
174                        GeneralPath path = calcPolygonPath( projection, (Surface) geom );
175                        if ( path != null ) {
176                            drawPolygon( g, path );
177                        } else {
178                            LOG.logWarning( "null path in " + this.getClass().getName() );
179                        }
180                    } else {
181                        MultiPrimitive msurface = (MultiPrimitive) geom;
182                        for ( int i = 0; i < msurface.getSize(); i++ ) {
183                            Primitive prim = msurface.getPrimitiveAt( i );
184                            if ( prim instanceof Surface ) {
185                                GeneralPath path = calcPolygonPath( projection, (Surface) prim );
186                                if ( path != null ) {
187                                    drawPolygon( g, path );
188                                } else {
189                                    LOG.logWarning( "null path in " + this.getClass().getName() );
190                                }
191                            } else {
192                                System.out.println( prim.getClass().getName() );
193                            }
194                        }
195                    }
196                } catch ( FilterEvaluationException e ) {
197                    LOG.logError( "FilterEvaluationException caught evaluating an Expression!", e );
198                } catch ( Exception ex ) {
199                    LOG.logError( "Exception caught evaluating an Expression!", ex );
200                }
201            }
202        }
203    
204        private double distance( Position p1, Position p2 ) {
205            double x1 = p1.getX();
206            double y1 = p1.getY();
207            double x2 = p2.getX();
208            double y2 = p2.getY();
209            return Math.sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) );
210        }
211    
212        private GeneralPath calcPolygonPath( GeoTransform projection, Surface surface )
213                                throws Exception {
214            GeneralPath path = new GeneralPath();
215    
216            SurfacePatch patch = surface.getSurfacePatchAt( 0 );
217            if ( patch == null )
218                return null;
219            appendRingToPath( path, patch.getExteriorRing(), projection );
220            Position[][] inner = patch.getInteriorRings();
221            if ( inner != null ) {
222                for ( int i = 0; i < inner.length; i++ ) {
223                    appendRingToPath( path, inner[i], projection );
224                }
225            }
226    
227            return path;
228        }
229    
230        private void appendRingToPath( GeneralPath path, Position[] ring, GeoTransform projection ) {
231            if ( ring.length == 0 )
232                return;
233    
234            int[] x = new int[ring.length];
235            int[] y = new int[ring.length];
236            int k = 0;
237    
238            Position p = projection.getDestPoint( ring[0] );
239            Position pp = p;
240            path.moveTo( (float) p.getX(), (float) p.getY() );
241            for ( int i = 1; i < ring.length; i++ ) {
242                p = projection.getDestPoint( ring[i] );
243                if ( distance( p, pp ) > 1 ) {
244                    path.lineTo( (float) p.getX(), (float) p.getY() );
245                    pp = p;
246                    x[k] = (int) p.getX();
247                    y[k++] = (int) p.getY();
248                }
249            }
250            path.closePath();
251            int[][] tmp = new int[3][];
252            tmp[0] = x;
253            tmp[1] = y;
254            tmp[2] = new int[] { k };
255            pathes.add( tmp );
256        }
257    
258        private void drawPolygon( Graphics g, GeneralPath path )
259                                throws FilterEvaluationException {
260            Graphics2D g2 = (Graphics2D) g;
261    
262            PolygonSymbolizer sym = (PolygonSymbolizer) symbolizer;
263            org.deegree.graphics.sld.Fill fill = sym.getFill();
264            org.deegree.graphics.sld.Stroke stroke = sym.getStroke();
265    
266            if ( fill != null ) {
267                double opacity = fill.getOpacity( feature );
268    
269                // is completly transparent
270                // if not fill polygon
271                if ( opacity > 0.01 ) {
272                    Color color = fill.getFill( feature );
273                    int alpha = (int) Math.round( opacity * 255 );
274                    int red = color.getRed();
275                    int green = color.getGreen();
276                    int blue = color.getBlue();
277                    color = new Color( red, green, blue, alpha );
278                    g2.setColor( color );
279                    GraphicFill gFill = fill.getGraphicFill();
280    
281                    if ( gFill != null ) {
282                        BufferedImage texture = gFill.getGraphic().getAsImage( feature );
283                        if ( texture != null ) {
284                            Rectangle anchor = new Rectangle( 0, 0, texture.getWidth( null ), texture.getHeight( null ) );
285                            g2.setPaint( new TexturePaint( texture, anchor ) );
286                        }
287                    }
288                    try {
289                        g2.fill( path );
290                    } catch ( Exception e ) {
291                        // why are all exceptions catched here?
292                    }
293                }
294            }
295    
296            // only stroke outline, if Stroke-Element is given
297            if ( stroke != null ) {
298                if ( stroke.getOpacity( feature ) > 0.001 ) {
299                    // do not paint if feature is completly transparent
300                    drawLine( g2, path, stroke );
301                }
302                if ( stroke.getGraphicStroke() != null ) {
303                    try {
304                        Image image = stroke.getGraphicStroke().getGraphic().getAsImage( feature );
305                        CurveWalker walker = new CurveWalker( g.getClipBounds() );
306    
307                        int[][] pos = null;
308                        for ( int i = 0; i < pathes.size(); i++ ) {
309                            pos = pathes.get( i );
310                            ArrayList<double[]> positions = walker.createPositions( pos, image.getWidth( null ), true );
311                            Iterator<double[]> it = positions.iterator();
312                            while ( it.hasNext() ) {
313                                double[] label = it.next();
314                                int x = (int) ( label[0] + 0.5 );
315                                int y = (int) ( label[1] + 0.5 );
316                                paintImage( image, g2, x, y, Math.toRadians( label[2] ) );
317                            }
318                        }
319                    } catch ( Exception e ) {
320                        LOG.logError( e.getMessage(), e );
321                    }
322                }
323    
324            }
325            pathes.clear();
326        }
327    
328        /**
329         * Renders a curve to the submitted graphic context.
330         * 
331         * TODO: Calculate miterlimit.
332         */
333        private void drawLine( Graphics g, GeneralPath path, org.deegree.graphics.sld.Stroke stroke )
334                                throws FilterEvaluationException {
335    
336            // Color & Opacity
337            Graphics2D g2 = (Graphics2D) g;
338            setColor( g2, stroke.getStroke( feature ), stroke.getOpacity( feature ) );
339    
340            float[] dash = stroke.getDashArray( feature );
341    
342            // use a simple Stroke if dash == null or its length < 2
343            // that's faster
344            float width = (float) stroke.getWidth( feature );
345            int cap = stroke.getLineCap( feature );
346            int join = stroke.getLineJoin( feature );
347            BasicStroke bs2 = null;
348    
349            if ( ( dash == null ) || ( dash.length < 2 ) ) {
350                bs2 = new BasicStroke( width, cap, join );
351            } else {
352                bs2 = new BasicStroke( width, cap, join, 10.0f, dash, stroke.getDashOffset( feature ) );
353            }
354    
355            g2.setStroke( bs2 );
356            g2.draw( path );
357    
358        }
359    
360        /**
361         * 
362         * 
363         * @param g2
364         * @param color
365         * @param opacity
366         * 
367         * @return the graphics object
368         */
369        private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
370            if ( opacity < 0.999 ) {
371                // just use a color having an alpha channel if a significant
372                // level of transparency has been defined
373                final int alpha = (int) Math.round( opacity * 255 );
374                final int red = color.getRed();
375                final int green = color.getGreen();
376                final int blue = color.getBlue();
377                color = new Color( red, green, blue, alpha );
378            }
379    
380            g2.setColor( color );
381            return g2;
382        }
383    
384        /**
385         * 
386         * @param image
387         * @param g
388         * @param x
389         * @param y
390         * @param rotation
391         */
392        private void paintImage( Image image, Graphics2D g, int x, int y, double rotation ) {
393    
394            // get the current transform
395            AffineTransform saveAT = g.getTransform();
396    
397            // translation parameters (rotation)
398            AffineTransform transform = new AffineTransform();
399            transform.rotate( rotation, x, y );
400            transform.translate( -image.getWidth( null ), -image.getHeight( null ) / 2.0 );
401            g.setTransform( transform );
402    
403            // render the image
404            g.drawImage( image, x, y, null );
405    
406            // restore original transform
407            g.setTransform( saveAT );
408        }
409    }