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 }