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