001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/graphics/displayelements/LineStringDisplayElement.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.geom.AffineTransform;
052    import java.io.Serializable;
053    import java.util.ArrayList;
054    import java.util.Iterator;
055    
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.graphics.sld.LineSymbolizer;
059    import org.deegree.graphics.sld.Symbolizer;
060    import org.deegree.graphics.transformation.GeoTransform;
061    import org.deegree.model.feature.Feature;
062    import org.deegree.model.filterencoding.FilterEvaluationException;
063    import org.deegree.model.spatialschema.Curve;
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.LineString;
068    import org.deegree.model.spatialschema.MultiCurve;
069    import org.deegree.model.spatialschema.Position;
070    import org.deegree.model.spatialschema.Surface;
071    
072    /**
073     * {@link DisplayElement} that encapsulates a linestring or multi-linestring geometry and a
074     * {@link LineSymbolizer}.
075     * <p>
076     * It can be rendered using a solid stroke or a graphics stroke.
077     * 
078     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
079     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider</a>
080     * @author last edited by: $Author: apoth $
081     * 
082     * @version $Revision: 9340 $, $Date: 2007-12-27 13:32:12 +0100 (Do, 27 Dez 2007) $
083     */
084    class LineStringDisplayElement extends GeometryDisplayElement implements DisplayElement, Serializable {
085    
086        private static final ILogger LOG = LoggerFactory.getLogger( LineStringDisplayElement.class );
087    
088        /** Use serialVersionUID for interoperability. */
089        private final static long serialVersionUID = -4657962592230618248L;
090    
091        /**
092         * Creates a new {@link LineStringDisplayElement} object.
093         * 
094         * @param feature
095         * @param geometry
096         */
097        public LineStringDisplayElement( Feature feature, Curve geometry ) {
098            super( feature, geometry, null );
099    
100            Symbolizer defaultSymbolizer = new LineSymbolizer();
101            this.setSymbolizer( defaultSymbolizer );
102        }
103    
104        /**
105         * Creates a new {@link LineStringDisplayElement} object.
106         * 
107         * @param feature
108         * @param geometry
109         * @param symbolizer
110         */
111        public LineStringDisplayElement( Feature feature, Curve geometry, LineSymbolizer symbolizer ) {
112            super( feature, geometry, symbolizer );
113        }
114    
115        /**
116         * Creates a new {@link LineStringDisplayElement} object.
117         * 
118         * @param feature
119         * @param geometry
120         */
121        public LineStringDisplayElement( Feature feature, MultiCurve geometry ) {
122            super( feature, geometry, null );
123    
124            Symbolizer defaultSymbolizer = new LineSymbolizer();
125            this.setSymbolizer( defaultSymbolizer );
126        }
127    
128        /**
129         * Creates a new {@link LineStringDisplayElement} object.
130         * 
131         * @param feature
132         * @param geometry
133         * @param symbolizer
134         */
135        public LineStringDisplayElement( Feature feature, MultiCurve geometry, LineSymbolizer symbolizer ) {
136            super( feature, geometry, symbolizer );
137        }
138    
139        /**
140         * Draws a graphics symbol (image) onto a defined position on the line.
141         * 
142         * @param image
143         * @param g
144         * @param x
145         * @param y
146         * @param rotation
147         */
148        private void paintImage( Image image, Graphics2D g, int x, int y, double rotation ) {
149    
150            // get the current transform
151            AffineTransform saveAT = g.getTransform();
152    
153            // translation parameters (rotation)
154            AffineTransform transform = new AffineTransform();
155            transform.rotate( rotation, x, y );
156            transform.translate( -image.getWidth( null ), -image.getHeight( null ) / 2.0 );
157            g.setTransform( transform );
158    
159            // render the image
160            g.drawImage( image, x, y, null );
161    
162            // restore original transform
163            g.setTransform( saveAT );
164        }
165    
166        /**
167         * Renders the DisplayElement to the submitted graphic context.
168         * 
169         * @param g
170         * @param projection
171         * @param scale
172         */
173        public void paint( Graphics g, GeoTransform projection, double scale ) {
174            synchronized ( symbolizer ) {
175                if ( geometry == null ) {
176                    return;
177                }
178    
179                // a local instance must be used because the following intersection operation may change
180                // the
181                // geometry type
182                Geometry geom = geometry;
183                try {
184                    Envelope screenRect = projection.getSourceRect();
185                    // grow by 5 percent to cope with partly overlapping stroke graphics
186                    screenRect = growEnvelope( screenRect, 0.05f );
187                    Surface sur = GeometryFactory.createSurface( screenRect, null );
188                    geom = sur.intersection( geometry );
189                } catch ( Exception e ) {
190                    // use original geometry
191                }
192                if ( geom == null ) {
193                    return;
194                }
195    
196                LOG.logDebug( "After clipping: " + geom.getClass().getName() );
197    
198                ( (ScaledFeature) feature ).setScale( scale );
199    
200                LineSymbolizer sym = (LineSymbolizer) symbolizer;
201                org.deegree.graphics.sld.Stroke stroke = sym.getStroke();
202    
203                // no stroke defined -> don't draw anything
204                if ( stroke == null ) {
205                    return;
206                }
207    
208                try {
209                    if ( stroke.getOpacity( feature ) > 0.001 ) {
210                        // do not paint if feature is completly transparent
211                        int[][] pos = null;
212                        Graphics2D g2 = (Graphics2D) g;
213    
214                        if ( geom instanceof Curve ) {
215                            pos = calcTargetCoordinates( projection, (Curve) geom );
216                            drawLine( g2, pos, stroke );
217                        } else {
218                            MultiCurve mc = (MultiCurve) geom;
219                            for ( int i = 0; i < mc.getSize(); i++ ) {
220                                pos = calcTargetCoordinates( projection, mc.getCurveAt( i ) );
221                                drawLine( g2, pos, stroke );
222                            }
223                        }
224                    }
225                } catch ( Exception e ) {
226                    LOG.logError( e.getMessage(), e );
227                }
228    
229                // GraphicStroke label
230                if ( stroke.getGraphicStroke() != null ) {
231                    try {
232                        Image image = stroke.getGraphicStroke().getGraphic().getAsImage( feature );
233                        CurveWalker walker = new CurveWalker( g.getClipBounds() );
234    
235                        if ( geom instanceof Curve ) {
236                            int[][] pos = LabelFactory.calcScreenCoordinates( projection, (Curve) geom );
237                            ArrayList<double[]> positions = walker.createPositions( pos, image.getWidth( null ), false );
238                            Iterator<double[]> it = positions.iterator();
239                            while ( it.hasNext() ) {
240                                double[] label = it.next();
241                                int x = (int) ( label[0] + 0.5 );
242                                int y = (int) ( label[1] + 0.5 );
243                                paintImage( image, (Graphics2D) g, x, y, Math.toRadians( label[2] ) );
244                            }
245                        } else {
246                            MultiCurve mc = (MultiCurve) geom;
247                            for ( int i = 0; i < mc.getSize(); i++ ) {
248                                int[][] pos = LabelFactory.calcScreenCoordinates( projection, mc.getCurveAt( i ) );
249                                ArrayList<double[]> positions = walker.createPositions( pos, image.getWidth( null ), false );
250                                Iterator<double[]> it = positions.iterator();
251                                while ( it.hasNext() ) {
252                                    double[] label = it.next();
253                                    int x = (int) ( label[0] + 0.5 );
254                                    int y = (int) ( label[1] + 0.5 );
255                                    paintImage( image, (Graphics2D) g, x, y, Math.toRadians( label[2] ) );
256                                }
257                            }
258                        }
259                    } catch ( Exception e ) {
260                        LOG.logError( e.getMessage(), e );
261                    }
262                }
263            }
264        }
265    
266        /**
267         * Calculates the screen coordinates of the curve.
268         */
269        private int[][] calcTargetCoordinates( GeoTransform projection, Curve curve )
270                                throws Exception {
271            LineString lineString = curve.getAsLineString();
272            int count = lineString.getNumberOfPoints();
273            int[][] pos = new int[3][];
274            pos[0] = new int[count];
275            pos[1] = new int[count];
276            pos[2] = new int[1];
277    
278            int k = 0;
279            for ( int i = 0; i < count; i++ ) {
280                Position position = lineString.getPositionAt( i );
281                double tx = projection.getDestX( position.getX() );
282                double ty = projection.getDestY( position.getY() );
283    
284                if ( i > 0 ) {
285                    if ( distance( tx, ty, pos[0][k - 1], pos[1][k - 1] ) > 1 ) {
286                        pos[0][k] = (int) ( tx + 0.5 );
287                        pos[1][k] = (int) ( ty + 0.5 );
288                        k++;
289                    }
290                } else {
291                    pos[0][k] = (int) ( tx + 0.5 );
292                    pos[1][k] = (int) ( ty + 0.5 );
293                    k++;
294                }
295            }
296            pos[2][0] = k;
297    
298            return pos;
299        }
300    
301        /**
302         * Renders a curve to the submitted graphic context.
303         * 
304         * TODO: Calculate miterlimit.
305         */
306        private void drawLine( Graphics g, int[][] pos, org.deegree.graphics.sld.Stroke stroke )
307                                throws FilterEvaluationException {
308    
309            // Color & Opacity
310            Graphics2D g2 = (Graphics2D) g;
311            setColor( g2, stroke.getStroke( feature ), stroke.getOpacity( feature ) );
312    
313            float[] dash = stroke.getDashArray( feature );
314    
315            // use a simple Stroke if dash == null or its length < 2
316            // that's faster
317            float width = (float) stroke.getWidth( feature );
318            int cap = stroke.getLineCap( feature );
319            int join = stroke.getLineJoin( feature );
320            BasicStroke bs2 = null;
321    
322            if ( ( dash == null ) || ( dash.length < 2 ) ) {
323                bs2 = new BasicStroke( width, cap, join );
324            } else {
325                bs2 = new BasicStroke( width, cap, join, 10.0f, dash, stroke.getDashOffset( feature ) );
326            }
327    
328            g2.setStroke( bs2 );
329    
330            g2.drawPolyline( pos[0], pos[1], pos[2][0] );
331    
332        }
333    
334        private double distance( double x1, double y1, double x2, double y2 ) {
335            return Math.sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) );
336        }
337    
338        private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
339            if ( opacity < 0.999 ) {
340                // just use a color having an alpha channel if a significant
341                // level of transparency has been defined
342                final int alpha = (int) Math.round( opacity * 255 );
343                final int red = color.getRed();
344                final int green = color.getGreen();
345                final int blue = color.getBlue();
346                color = new Color( red, green, blue, alpha );
347            }
348    
349            g2.setColor( color );
350            return g2;
351        }
352    }