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 }