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 }