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 }