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