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