001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/graphics/displayelements/LineStringDisplayElement.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 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: wanhoff $
081     * 
082     * @version $Revision: 6394 $, $Date: 2007-03-26 15:11:18 +0200 (Mo, 26 Mär 2007) $
083     */
084    class LineStringDisplayElement extends GeometryDisplayElement implements DisplayElement,
085                                                                 Serializable {
086    
087        private static final ILogger LOG = LoggerFactory.getLogger( LineStringDisplayElement.class );
088    
089        /** Use serialVersionUID for interoperability. */
090        private final static long serialVersionUID = -4657962592230618248L;
091    
092        /**
093         * Creates a new {@link LineStringDisplayElement} object.
094         * 
095         * @param feature
096         * @param geometry
097         */
098        public LineStringDisplayElement( Feature feature, Curve geometry ) {
099            super( feature, geometry, null );
100    
101            Symbolizer defaultSymbolizer = new LineSymbolizer();
102            this.setSymbolizer( defaultSymbolizer );
103        }
104    
105        /**
106         * Creates a new {@link LineStringDisplayElement} object.
107         * 
108         * @param feature
109         * @param geometry
110         * @param symbolizer
111         */
112        public LineStringDisplayElement( Feature feature, Curve geometry, LineSymbolizer symbolizer ) {
113            super( feature, geometry, symbolizer );
114        }
115    
116        /**
117         * Creates a new {@link LineStringDisplayElement} object.
118         * 
119         * @param feature
120         * @param geometry
121         */
122        public LineStringDisplayElement( Feature feature, MultiCurve geometry ) {
123            super( feature, geometry, null );
124    
125            Symbolizer defaultSymbolizer = new LineSymbolizer();
126            this.setSymbolizer( defaultSymbolizer );
127        }
128    
129        /**
130         * Creates a new {@link LineStringDisplayElement} object.
131         * 
132         * @param feature
133         * @param geometry
134         * @param symbolizer
135         */
136        public LineStringDisplayElement( Feature feature, MultiCurve geometry, LineSymbolizer symbolizer ) {
137            super( feature, geometry, symbolizer );
138        }
139    
140        /**
141         * Draws a graphics symbol (image) onto a defined position on the line.
142         * 
143         * @param image
144         * @param g
145         * @param x
146         * @param y
147         * @param rotation
148         */
149        private void paintImage( Image image, Graphics2D g, int x, int y, double rotation ) {
150    
151            // get the current transform
152            AffineTransform saveAT = g.getTransform();
153    
154            // translation parameters (rotation)
155            AffineTransform transform = new AffineTransform();
156            transform.rotate( rotation, x, y );
157            transform.translate( -image.getWidth( null ), -image.getHeight( null ) / 2.0 );
158            g.setTransform( transform );
159    
160            // render the image
161            g.drawImage( image, x, y, null );
162    
163            // restore original transform
164            g.setTransform( saveAT );
165        }
166    
167        /**
168         * Renders the DisplayElement to the submitted graphic context.
169         * 
170         * @param g
171         * @param projection
172         * @param scale
173         */
174        public void paint( Graphics g, GeoTransform projection, double scale ) {
175    
176            if ( geometry == null ) {
177                return;
178            }
179    
180            // a local instance must be used because the following intersection operation may change 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 positions = walker.createPositions( pos, image.getWidth( null ) );
238                        Iterator it = positions.iterator();
239                        while ( it.hasNext() ) {
240                            double[] label = (double[]) 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,
249                                                                              mc.getCurveAt( i ) );
250                            ArrayList positions = walker.createPositions( pos, image.getWidth( null ) );
251                            Iterator it = positions.iterator();
252                            while ( it.hasNext() ) {
253                                double[] label = (double[]) it.next();
254                                int x = (int) ( label[0] + 0.5 );
255                                int y = (int) ( label[1] + 0.5 );
256                                paintImage( image, (Graphics2D) g, x, y, Math.toRadians( label[2] ) );
257                            }
258                        }
259                    }
260                } catch ( Exception e ) {
261                    LOG.logError( e.getMessage(), e );
262                }
263            }
264        }
265    
266        /**
267         * Returns a new {@link Envelope} for the given envelope that has a border of percent * (with |
268         * height) on all sides around it (the longer side is used to determine the border size).
269         * 
270         * @param env
271         * @param percent
272         * @return envelope with border around it
273         */
274        private Envelope growEnvelope( Envelope env, float percent ) {
275            Position minPos = env.getMin();
276            Position maxPos = env.getMax();
277            double h = maxPos.getX() - minPos.getX();
278            double w = maxPos.getY() - minPos.getY();
279            h = Math.abs( h );
280            w = Math.abs( w );
281            double maxSide = Math.max( w, h );
282            return env.getBuffer( maxSide * percent );
283        }
284    
285        /**
286         * Calculates the screen coordinates of the curve.
287         */
288        private int[][] calcTargetCoordinates( GeoTransform projection, Curve curve )
289                                throws Exception {
290            LineString lineString = curve.getAsLineString();
291            int count = lineString.getNumberOfPoints();
292            int[][] pos = new int[3][];
293            pos[0] = new int[count];
294            pos[1] = new int[count];
295            pos[2] = new int[1];
296    
297            int k = 0;
298            for ( int i = 0; i < count; i++ ) {
299                Position position = lineString.getPositionAt( i );
300                double tx = projection.getDestX( position.getX() );
301                double ty = projection.getDestY( position.getY() );
302    
303                if ( i > 0 ) {
304                    if ( distance( tx, ty, pos[0][k - 1], pos[1][k - 1] ) > 1 ) {
305                        pos[0][k] = (int) ( tx + 0.5 );
306                        pos[1][k] = (int) ( ty + 0.5 );
307                        k++;
308                    }
309                } else {
310                    pos[0][k] = (int) ( tx + 0.5 );
311                    pos[1][k] = (int) ( ty + 0.5 );
312                    k++;
313                }
314            }
315            pos[2][0] = k;
316    
317            return pos;
318        }
319    
320        /**
321         * Renders a curve to the submitted graphic context.
322         * 
323         * TODO: Calculate miterlimit.
324         */
325        private void drawLine( Graphics g, int[][] pos, org.deegree.graphics.sld.Stroke stroke )
326                                throws FilterEvaluationException {
327    
328            // Color & Opacity
329            Graphics2D g2 = (Graphics2D) g;
330            setColor( g2, stroke.getStroke( feature ), stroke.getOpacity( feature ) );
331    
332            float[] dash = stroke.getDashArray( feature );
333    
334            // use a simple Stroke if dash == null or its length < 2
335            // that's faster
336            float width = (float) stroke.getWidth( feature );
337            int cap = stroke.getLineCap( feature );
338            int join = stroke.getLineJoin( feature );
339            BasicStroke bs2 = null;
340    
341            if ( ( dash == null ) || ( dash.length < 2 ) ) {
342                bs2 = new BasicStroke( width, cap, join );
343            } else {
344                bs2 = new BasicStroke( width, cap, join, 10.0f, dash, stroke.getDashOffset( feature ) );
345            }
346    
347            g2.setStroke( bs2 );
348    
349            g2.drawPolyline( pos[0], pos[1], pos[2][0] );
350    
351        }
352    
353        private double distance( double x1, double y1, double x2, double y2 ) {
354            return Math.sqrt( ( x2 - x1 ) * ( x2 - x1 ) + ( y2 - y1 ) * ( y2 - y1 ) );
355        }
356    
357        private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
358            if ( opacity < 0.999 ) {
359                // just use a color having an alpha channel if a significant
360                // level of transparency has been defined
361                final int alpha = (int) Math.round( opacity * 255 );
362                final int red = color.getRed();
363                final int green = color.getGreen();
364                final int blue = color.getBlue();
365                color = new Color( red, green, blue, alpha );
366            }
367    
368            g2.setColor( color );
369            return g2;
370        }
371    }