/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.graphics.legend;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.graphics.displayelements.DisplayElementFactory;
import org.deegree.graphics.displayelements.IncompatibleGeometryTypeException;
import org.deegree.graphics.displayelements.PolygonDisplayElement;
import org.deegree.graphics.sld.LineSymbolizer;
import org.deegree.graphics.sld.PointSymbolizer;
import org.deegree.graphics.sld.PolygonSymbolizer;
import org.deegree.graphics.sld.RasterSymbolizer;
import org.deegree.graphics.sld.Rule;
import org.deegree.graphics.sld.Symbolizer;
import org.deegree.graphics.sld.TextSymbolizer;
import org.deegree.graphics.transformation.WorldToScreenTransform;
import org.deegree.model.filterencoding.FilterEvaluationException;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.GeometryFactory;
import org.deegree.model.spatialschema.Position;
import org.deegree.model.spatialschema.Surface;
import org.deegree.ogcwebservices.wms.GraphicContextFactory;
/**
* The implements the basic legend element. a legend element may has a
* label that can be set to eight positions relative to the legend graphic. A
* <tt>LegendElement</tt> can be activated or deactivated. It depends on the
* using application what effect this behavior will have. <p>
* <tt>LegendElement</tt>s can be collected in a <tt>LegendElementCollection</tt>
* which also is a <tt>LegendElement</tt> to group elements or to create more
* complex elements.<p>
* Each <tt>LegendElement</tt> is able to paint itself as <tt>BufferedImage</tt>
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @version $Revision: 1.17 $ $Date: 2006/11/28 12:49:54 $
*/
public class LegendElement {
private static final ILogger LOG = LoggerFactory.getLogger( LegendElement.class );
protected ArrayList ruleslist = null;
protected String label = "";
protected double orientation = 0;
protected int labelPosition = -1;
protected boolean active = false;
protected int width = 0;
protected int height = 0;
protected int bufferBetweenLegendAndLabel = 10;
protected BufferedImage bi = null;
/**
* empty constructor
*
*/
protected LegendElement() {
this.ruleslist = new ArrayList();
}
/**
*
*
*/
LegendElement( BufferedImage legendImage ) {
this();
bi = legendImage;
}
/**
* constructor
* @param rules the different rules from the SLD
* @param label the label beneath the legend symbol
* @param orientation the rotation of the text in the legend
* @param labelPosition the position of the text according to the symbol
* @param active whether the legendsymbol is active or not
* @param width the requested width of the legend symbol
* @param height the requested height of the legend symbol
*/
LegendElement( Rule[] rules, String label, double orientation, int labelPosition,
boolean active, int width, int height ) {
this();
setRules( rules );
setLabel( label );
setLabelOrientation( orientation );
setLabelPlacement( labelPosition );
setActive( active );
setWidth( width );
setHeight( height );
}
/**
* gets the Rules as an array
* @return array of sld rules
*/
public Rule[] getRules() {
if ( ruleslist != null && ruleslist.size() > 0 ) {
return (Rule[]) ruleslist.toArray( new Rule[ruleslist.size()] );
}
return null;
}
/**
* adds a rule to the ArrayList ruleslist
* @param rule a sld rule
*/
public void addRule( Rule rule ) {
this.ruleslist.add( rule );
}
/**
* sets the rules
* @param rules an array of sld rules
*/
public void setRules( Rule[] rules ) {
this.ruleslist.clear();
if ( rules != null ) {
for ( int i = 0; i < rules.length; i++ ) {
this.ruleslist.add( rules[i] );
}
}
}
/**
* sets the label of the <tt>LegendElement</tt>
*
* @param label label of the <tt>LegendElement</tt>
*
*/
public void setLabel( String label ) {
this.label = label;
}
/**
* returns the label set to <tt>LegendElement</tt>. If no label is set, the
* method returns <tt>null</tt>
*
* @return label of the <tt>LegendElement</tt> or <tt>null</tt>
*
*/
public String getLabel() {
return this.label;
}
/**
* sets the orientation of the label of the <tt>LegendElement</tt>. A label
* can have an orientation from -90� to 90� expressed in radians, where 0�
* is horizontal
*
* @param orientation
*/
public void setLabelOrientation( double orientation ) {
this.orientation = orientation;
}
/**
* returns the current orientation of the label of the <tt>LegendElement</tt>
* in radians. If the element hasn't a label <tt>Double.NEGATIVE_INFINITY</tt>
* will be returned.
*
* @return orientation of the label of the <tt>LegendElement</tt> in radians
*/
public double getLabelOrientation() {
return this.orientation;
}
/**
* sets the placement of the label relative to the legend symbol. Possible
* values are:
* <ul>
* <li>LP_TOPCENTER
* <li>LP_TOPLEFT
* <li>LP_TOPRIGHT
* <li>LP_RIGHT
* <li>LP_LEFT
* <li>LP_BOTTOMCENTER
* <li>LP_BOTTOMRIGHT
* <li>LP_BOTTOMLEFT
* </ul>
* <pre>
* +---+---+---+
* | 1 | 0 | 2 |
* +---+---+---+
* | 4 |LEG| 3 |
* +---+---+---+
* | 7 | 5 | 6 |
* +---+---+---+
* </pre>
* An implementation of the interface may not supoort all positions.
*
* @param labelPosition
*/
public void setLabelPlacement( int labelPosition ) {
this.labelPosition = labelPosition;
}
/**
* returns the placement of the label relative to the legend symbol. If the
* element hasn't a label <tt>LegendElement.LP_NOLABEL</tt> will be returned.
* Otherwise possible values are:
* <ul>
* <li>LP_TOPCENTER
* <li>LP_TOPLEFT
* <li>LP_TOPRIGHT
* <li>LP_RIGHT
* <li>LP_LEFT
* <li>LP_BOTTOMCENTER
* <li>LP_BOTTOMRIGHT
* <li>LP_BOTTOMLEFT
* </ul>
*
* @return coded placement of the label relative to the legend symbol
*/
public int getLabelPlacement() {
return this.labelPosition;
}
/**
* activates or deactivates the label
*
* @param active
*/
public void setActive( boolean active ) {
this.active = active;
}
/**
* @return the activtion-status of the label
*/
public boolean isActive() {
return this.active;
}
/**
* sets the width of the LegendSymbol (in pixels)
* @param width
*/
public void setWidth( int width ) {
this.width = width;
}
/**
* @return the width of the LegendSymbol (in pixels)
*/
public int getWidth() {
return this.width;
}
/**
* sets the height of the LegendSymbol (in pixels)
* @param height
*/
public void setHeight( int height ) {
this.height = height;
}
/**
* @return the height of the LegendSymbol (in pixels)
*/
public int getHeight() {
return this.height;
}
/**
* returns the buffer place between the legend symbol and the legend label in pixels
* @return the buffer as integer in pixels
*/
public int getBufferBetweenLegendAndLabel() {
return this.bufferBetweenLegendAndLabel;
}
/**
* @see org.deegree.graphics.legend.LegendElement#getBufferBetweenLegendAndLabel()
* @param i the buffer as integer in pixels
*/
public void setBufferBetweenLegendAndLabel( int i ) {
this.bufferBetweenLegendAndLabel = i;
}
/**
* draws a legendsymbol, if the SLD defines a point
* @param g the graphics context
* @param c the PointSymbolizer representing the drawable point
* @param width the requested width of the symbol
* @param height the requested height of the symbol
* @throws LegendException is thrown, if the parsing of the sld failes.
*/
protected void drawPointLegend( Graphics g, PointSymbolizer c, int width, int height )
throws LegendException {
org.deegree.graphics.sld.Graphic deegreegraphic = c.getGraphic();
try {
BufferedImage buffi = deegreegraphic.getAsImage( null );
int w = buffi.getWidth();
int h = buffi.getHeight();
g.drawImage( buffi, width / 2 - w / 2, height / 2 - h / 2, null );
} catch ( FilterEvaluationException feex ) {
throw new LegendException( "FilterEvaluationException occured during "
+ "the creation of the legend:\n"
+ "The legend for the PointSymbol can't be processed.\n"
+ feex.getMessage() );
}
}
/**
* draws a legendsymbol, if the SLD defines a line
* @param g the graphics context
* @param ls the LineSymbolizer representing the drawable line
* @param width the requested width of the symbol
* @param height the requested height of the symbol
* @throws LegendException is thrown, if the parsing of the sld failes.
*/
protected void drawLineStringLegend( Graphics2D g, LineSymbolizer ls, int width, int height )
throws LegendException {
org.deegree.graphics.sld.Stroke sldstroke = ls.getStroke();
try {
// color, opacity
setColor( g, sldstroke.getStroke( null ), sldstroke.getOpacity( null ) );
g.setStroke( getBasicStroke( sldstroke ) );
} catch ( FilterEvaluationException feex ) {
throw new LegendException( "FilterEvaluationException occured during the creation "
+ "of the legend:\n The legend for the LineSymbol can't be "
+ "processed.\n" + feex.getMessage() );
}
// p1 = [0 | height]
// p2 = [width / 3 | height / 3]
// p3 = [width - width / 3 | height - height / 3]
// p4 = [width | 0]
int[] xPoints = { 0, width / 3, width - width / 3, width };
int[] yPoints = { height, height / 3, height - height / 3, 0 };
int nPoints = 4;
g.drawPolyline( xPoints, yPoints, nPoints );
}
/**
* draws a legendsymbol, if the SLD defines a polygon
* @param g the graphics context
* @param ps the PolygonSymbolizer representing the drawable polygon
* @param width the requested width of the symbol
* @param height the requested height of the symbol
* @throws LegendException if the parsing of the sld failes.
*/
protected void drawPolygonLegend( Graphics2D g, PolygonSymbolizer ps, int width, int height )
throws LegendException {
Position p1 = GeometryFactory.createPosition( 0, 0 );
Position p2 = GeometryFactory.createPosition( 0, height - 1 );
Position p3 = GeometryFactory.createPosition( width, height - 1 );
Position p4 = GeometryFactory.createPosition( width, 0 );
Position[] pos = { p1, p2, p3, p4, p1 };
Surface surface = null;
try {
surface = GeometryFactory.createSurface( pos, null, null, null );
} catch ( Exception ex ) {
throw new LegendException( "Exception occured during the creation of the legend:\n"
+ "The legendsymbol for the Polygon can't be processed.\n"
+ "Error in creating the Surface from the Positions.\n"
+ ex.getMessage() );
}
PolygonDisplayElement pde = null;
try {
// Feature, Geometry, PolygonSymbolizer
pde = DisplayElementFactory.buildPolygonDisplayElement( null, surface, ps );
} catch ( IncompatibleGeometryTypeException igtex ) {
throw new LegendException( "IncompatibleGeometryTypeException occured during "
+ "the creation of the legend:\n The legendsymbol for "
+ "the Polygon can't be processed.\nError in creating "
+ "the PolygonDisplayElement.\n" + igtex.getMessage() );
} catch ( Exception e ) {
throw new LegendException( "Could not create symbolizer:\n" + e.getMessage() );
}
Envelope envelope = GeometryFactory.createEnvelope( p1, p3, null );
WorldToScreenTransform wtst = new WorldToScreenTransform( envelope, envelope );
pde.paint( g, wtst, -1 );
}
/**
* sets the color including an opacity
* @param g2 the graphics contect as Graphics2D
* @param color the requested color of the legend symbol
* @param opacity the requested opacity of the legend symbol
* @return the Graphics2D object containing color and opacity
*/
private Graphics2D setColor( Graphics2D g2, Color color, double opacity ) {
if ( opacity < 0.999 ) {
final int alpha = (int) Math.round( opacity * 255 );
final int red = color.getRed();
final int green = color.getGreen();
final int blue = color.getBlue();
color = new Color( red, green, blue, alpha );
}
g2.setColor( color );
return g2;
}
/**
* constructs a java.awt.BasicStroke for painting a LineString legend symbol.
* @param sldstroke the deegree sld stroke
* @return a java.awt.BasicStroke
* @throws LegendException if the sld cannot be processed
*/
private BasicStroke getBasicStroke( org.deegree.graphics.sld.Stroke sldstroke )
throws LegendException {
BasicStroke bs = null;
try {
float width = (float) sldstroke.getWidth( null );
int cap = sldstroke.getLineCap( null );
int join = sldstroke.getLineJoin( null );
float miterlimit = 1f;
float[] dash = sldstroke.getDashArray( null );
float dash_phase = sldstroke.getDashOffset( null );
bs = new BasicStroke( width, cap, join, miterlimit, dash, dash_phase );
// return new BasicStroke((float)sldstroke.getWidth(null), sldstroke.getLineCap(null), sldstroke.getLineJoin(null), 1f, sldstroke.getDashArray(null), sldstroke.getDashOffset(null));
} catch ( FilterEvaluationException ex ) {
throw new LegendException(
"FilterEvaluationException occured during the creation of the legend:\n"
+ "The Stroke of the element can't be processed.\n"
+ ex.getMessage() );
}
return bs;
}
/**
* calculates the FontMetrics of the LegendLabel in pixels. It returns an 3-dimensional array
* containing [0] the width, [1] the ascent and [2] the descent.
* @param label the label of the LegendElement
* @return the 3-dimensional INT-Array contains [0] the width of the string, [1] the ascent and [2] the descent.
*/
protected int[] calculateFontMetrics( String label ) {
int[] fontmetrics = new int[3];
BufferedImage bi = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB );
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics();
if ( label != null && label.length() > 0 ) {
fontmetrics[0] = fm.stringWidth( label );
fontmetrics[1] = fm.getAscent();
fontmetrics[2] = fm.getDescent();
} else {
fontmetrics[0] = 0;
fontmetrics[1] = 0;
fontmetrics[2] = 0;
// value = 1, because of a bug, which doesn't paint the right border of the polygon-symbol.
setBufferBetweenLegendAndLabel( 1 );
}
g.dispose();
return fontmetrics;
}
/**
* calculates the width and height of the resulting LegendSymbol depending
* on the LabelPlacement
*/
private BufferedImage calculateImage( int labelposition, int labelwidth, int ascent,
int descent, int legendwidth, int legendheight,
int buffer, String mime ) {
// eliminate buffer if label width is zero, so pixel counting works for the reference
// implementation
if ( labelwidth == 0 ) {
buffer = 0;
}
BufferedImage bi = (BufferedImage) GraphicContextFactory.createGraphicTarget(
mime,
( legendwidth
+ buffer + labelwidth ),
legendheight );
Graphics g = bi.getGraphics();
g.setColor( Color.BLACK );
// TODO labelposition
switch ( labelposition ) {
// LP_TOPCENTER
case 0: {
LOG.logInfo( "The text-position LP_TOPCENTER in the legend is not "
+ "implemented yet.\n We put the text on the right side (EAST) of "
+ "the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_TOPLEFT
case 1: {
LOG.logInfo( "The text-position LP_TOPLEFT in the legend is not implemented "
+ "yet.\n We put the text on the right side (EAST) of the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_TOPRIGHT
case 2: {
LOG.logInfo( "The text-position LP_TOPRIGHT in the legend is not implemented "
+ "yet.\n We put the text on the right side (EAST) of the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_RIGHT
case 3: {
LOG.logInfo( "The text-position LP_RIGHT in the legend is not implemented "
+ "yet.\n We put the text on the right side (EAST) of the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_LEFT
case 4: {
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_BOTTOMCENTER
case 5: {
LOG.logInfo( "The text-position LP_BOTTOMCENTER in the legend is not "
+ "implemented yet.\n We put the text on the right side (EAST) of "
+ "the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_BOTTOMRIGHT
case 6: {
LOG.logInfo( "The text-position LP_BOTTOMRIGHT in the legend is not "
+ "implemented yet.\n We put the text on the right side (EAST) of "
+ "the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
// LP_BOTTOMLEFT
case 7: {
LOG.logInfo( "The text-position LP_BOTTOMLEFT in the legend is not implemented "
+ "yet.\n We put the text on the right side (EAST) of the legendsymbol." );
g.drawString( getLabel(), width + 10, height / 2 + ( ( ascent - descent ) / 2 ) );
break;
}
}
g.dispose();
return bi;
}
/**
* exports the <tt>LegendElement</tt> as </tt>BufferedImage</tt>
* @param mime
*
* @return the image
* @throws LegendException
*/
public BufferedImage exportAsImage( String mime )
throws LegendException {
if ( bi == null ) {
// calculates the fontmetrics and creates the bufferedimage
// if getLabel() is null is checked in calculateFontMetrics!
int[] fontmetrics = calculateFontMetrics( getLabel() );
bi = calculateImage( getLabelPlacement(), fontmetrics[0], fontmetrics[1],
fontmetrics[2], getWidth(), getHeight(),
getBufferBetweenLegendAndLabel(), mime );
Graphics g = bi.getGraphics();
((Graphics2D)g).setClip( 0, 0, getWidth(), getHeight() );
g.setColor( Color.WHITE );
Rule[] myrules = getRules();
Symbolizer[] symbolizer = null;
// determines the legendsymbol and paints it
for ( int a = 0; a < myrules.length; a++ ) {
symbolizer = myrules[a].getSymbolizers();
for ( int b = 0; b < symbolizer.length; b++ ) {
if ( symbolizer[b] instanceof PointSymbolizer ) {
drawPointLegend( g, (PointSymbolizer) symbolizer[b], getWidth(),
getHeight() );
}
if ( symbolizer[b] instanceof LineSymbolizer ) {
drawLineStringLegend( (Graphics2D) g, (LineSymbolizer) symbolizer[b],
width, height );
}
if ( symbolizer[b] instanceof PolygonSymbolizer ) {
drawPolygonLegend( (Graphics2D) g, (PolygonSymbolizer) symbolizer[b],
width, height );
}
if ( symbolizer[b] instanceof RasterSymbolizer ) {
// throw new LegendException("RasterSymbolizer is not implemented yet!");
}
if ( symbolizer[b] instanceof TextSymbolizer ) {
// throw new LegendException("TextSymbolizer is not implemented yet!");
}
}
// g.setColor(Color.black);
// g.drawString(getLabel(), width + 10, height / 2 + ((fontmetrics[1] - fontmetrics[2]) / 2));
}
} else {
if ( mime.equalsIgnoreCase( "image/gif" ) || mime.equalsIgnoreCase( "image/png" ) ) {
BufferedImage bii = new BufferedImage( bi.getWidth(), bi.getHeight(),
BufferedImage.TYPE_INT_ARGB );
Graphics g = bii.getGraphics();
g.drawImage( bi, 0, 0, null );
g.dispose();
bi = bii;
} else if ( mime.equalsIgnoreCase( "image/jpg" )
|| mime.equalsIgnoreCase( "image/jpeg" )
|| mime.equalsIgnoreCase( "image/tif" )
|| mime.equalsIgnoreCase( "image/tiff" )
|| mime.equalsIgnoreCase( "image/bmp" ) ) {
BufferedImage bii = new BufferedImage( bi.getWidth(), bi.getHeight(),
BufferedImage.TYPE_INT_RGB );
Graphics g = bii.getGraphics();
g.drawImage( bi, 0, 0, null );
g.dispose();
bi = bii;
} else if ( mime.equalsIgnoreCase( "image/svg+xml" ) ) {
}
}
return bi;
}
}
/* ***************************************************************************
Changes to this class. What the people have been up to:
$Log: LegendElement.java,v $
Revision 1.17 2006/11/28 12:49:54 poth
bug fix - returning correct height now
Revision 1.16 2006/11/27 12:57:35 poth
bug fix - setting graphic clip bounds for rendering polygon symbol
Revision 1.15 2006/10/27 09:52:24 schmitz
Brought the WMS up to date regarding 1.1.1 and 1.3.0 conformance.
Fixed a bug while creating the default GetLegendGraphics URLs.
Revision 1.14 2006/07/29 08:51:12 poth
references to deprecated classes removed
Revision 1.13 2006/06/06 13:19:29 poth
bug fix setting image size
Revision 1.12 2006/04/06 20:25:31 poth
*** empty log message ***
Revision 1.11 2006/04/04 20:39:44 poth
*** empty log message ***
Revision 1.10 2006/03/30 21:20:28 poth
*** empty log message ***
Revision 1.9 2005/12/06 13:45:20 poth
System.out.println substituted by logging api
Revision 1.8 2005/03/12 10:45:03 poth
no message
Revision 1.7 2005/03/09 11:55:47 mschneider
*** empty log message ***
Revision 1.6 2005/02/24 20:04:04 poth
no message
Revision 1.5 2005/02/21 18:04:19 poth
no message
Revision 1.4 2005/02/21 08:57:59 poth
no message
Revision 1.3 2005/02/18 20:54:18 poth
no message
Revision 1.2 2005/01/18 22:08:54 poth
no message
Revision 1.6 2004/07/23 07:14:45 ap
no message
**************************************************************************** */