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 }