/*---------------- 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.sld;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import org.deegree.framework.util.StringTools;
import org.deegree.framework.xml.Marshallable;
import org.deegree.model.feature.Feature;
import org.deegree.model.filterencoding.FilterEvaluationException;
/**
* A Mark takes a "shape" and applies coloring to it. The shape can be derived
* either from a well-known name (such as "square"), an external URL in various
* formats (such as, perhaps GIF), or from a glyph of a font. Multiple external
* formats may be used with the semantic that they all contain the equivalent
* shape in different formats. If an image format is used that has inherent
* coloring, the coloring is discarded and only the opacity channel (or
* equivalent) is used. A Halo, Fill, and/or Stroke is applied as appropriate
* for the shape's source format.
* <p>
*
* @author <a href="mailto:k.lupp@web.de">Katharina Lupp </a>
* @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
* @version $Revision: 1.14 $ $Date: 2006/11/24 14:59:27 $
*/
public class Mark implements Marshallable {
private BufferedImage image = null;
private Fill fill = null;
private String wellKnownName = null;
private Stroke stroke = null;
/**
* Constructor for the default <tt>Mark</tt>.
*/
Mark() {
}
/**
* constructor initializing the class with the <Mark>
*/
Mark( String wellKnownName, Stroke stroke, Fill fill ) {
setWellKnownName( wellKnownName );
setStroke( stroke );
setFill( fill );
}
/**
* Gives the well known name of a Mark's shape. Allowed values include at
* least "square", "circle", "triangle", "star", "cross", and "x", though
* map servers may draw a different symbol instead if they don't have a
* shape for all of these. Renderings of these marks may be made solid or
* hollow depending on Fill and Stroke parameters. The default value is
* "square".
*
* @return the WK-Name of the mark
*
*/
public String getWellKnownName() {
return wellKnownName;
}
/**
* Sets the well known name of a Mark's shape. Allowed values include at
* least "square", "circle", "triangle", "star", "cross", and "x", though
* map servers may draw a different symbol instead if they don't have a
* shape for all of these. Renderings of these marks may be made solid or
* hollow depending on Fill and Stroke parameters. The default value is
* "square".
*
* @param wellKnownName
* the WK-Name of the mark
*
*/
public void setWellKnownName( String wellKnownName ) {
this.wellKnownName = wellKnownName;
}
/**
* A Fill allows area geometries to be filled. There are two types of fills:
* solid-color and repeated GraphicFill. In general, if a Fill element is
* omitted in its containing element, no fill will be rendered. The default
* is a solid 50%-gray (color "#808080") opaque fill.
*
* @return the fill of the mark
*/
public Fill getFill() {
return fill;
}
/**
* sets the <Fill>
*
* @param fill
* the fill of the mark
*/
public void setFill( Fill fill ) {
this.fill = fill;
}
/**
* A Stroke allows a string of line segments (or any linear geometry) to be
* rendered. There are three basic types of strokes: solid Color,
* GraphicFill (stipple), and repeated GraphicStroke. A repeated graphic is
* plotted linearly and has its graphic symbol bended around the curves of
* the line string. The default is a solid black line (Color "#000000").
*
* @return the stroke of the mark
*/
public Stroke getStroke() {
return stroke;
}
/**
* sets <Stroke>
*
* @param stroke
* the stroke of the mark
*/
public void setStroke( Stroke stroke ) {
this.stroke = stroke;
}
/**
* DOCUMENT ME!
*
* @param size
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public BufferedImage getAsImage( Feature feature, int size )
throws FilterEvaluationException {
double fillOpacity = 1.0;
double strokeOpacity = 1.0;
float[] dash = null;
float dashOffset = 0;
int cap = BasicStroke.CAP_ROUND;
int join = BasicStroke.JOIN_ROUND;
float width = 1;
Color fillColor = new Color( 128, 128, 128 );
Color strokeColor = new Color( 0, 0, 0 );
if ( fill != null ) {
fillOpacity = fill.getOpacity( feature );
fillColor = fill.getFill( feature );
}
if ( stroke != null ) {
strokeOpacity = stroke.getOpacity( feature );
strokeColor = stroke.getStroke( feature );
dash = stroke.getDashArray( feature );
cap = stroke.getLineCap( feature );
join = stroke.getLineJoin( feature );
width = (float) stroke.getWidth( feature );
dashOffset = stroke.getDashOffset( feature );
}
if ( wellKnownName == null ) {
wellKnownName = "square";
}
if ( wellKnownName.equalsIgnoreCase( "circle" ) ) {
image = drawCircle( size, fillOpacity, fillColor, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
} else if ( wellKnownName.equalsIgnoreCase( "triangle" ) ) {
image = drawTriangle( size, fillOpacity, fillColor, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
} else if ( wellKnownName.equalsIgnoreCase( "cross" ) ) {
image = drawCross1( size, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
} else if ( wellKnownName.equalsIgnoreCase( "x" ) ) {
image = drawCross2( size, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
} else if ( wellKnownName.startsWith( "CHAR" ) ) {
image = drawCharacter( size, fillOpacity, fillColor, strokeOpacity, strokeColor,
wellKnownName );
} else if ( wellKnownName.equalsIgnoreCase( "star" ) ) {
image = drawStar( size, fillOpacity, fillColor, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
} else {
image = drawSquare( size, fillOpacity, fillColor, strokeOpacity, strokeColor, dash,
dashOffset, width, cap, join );
}
return image;
}
/**
* Sets the mark as an image. Rhis method is not part of the sld
* specifications but it is added to speed up applications.
*
* @param bufferedImage
* the bufferedImage to be set for the mark
*/
public void setAsImage( BufferedImage bufferedImage ) {
this.image = bufferedImage;
}
/**
*
* @param dash
* @param dashOffset
* @param width
* @param cap
* @param join
* @return
* @throws FilterEvaluationException
*/
private BasicStroke createBasicStroke( float[] dash, float dashOffset, float width, int cap,
int join ) {
BasicStroke bs2 = null;
if ( ( dash == null ) || ( dash.length < 2 ) ) {
bs2 = new BasicStroke( width, cap, join );
} else {
bs2 = new BasicStroke( width, cap, join, 10.0f, dash, dashOffset );
}
return bs2;
}
/**
* Draws a scaled instance of a triangle mark according to the given
* parameters.
*
* @param size
* resulting image's height and width
* @param fillOpacity
* opacity value for the filled parts of the image
* @param fillColor
* <tt>Color</tt> to be used for the fill
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return image displaying a triangle
*/
public BufferedImage drawTriangle( int size, double fillOpacity, Color fillColor,
double strokeOpacity, Color strokeColor, float[] dash,
float dashOffset, float width, int cap, int join ) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
int[] x_ = new int[3];
int[] y_ = new int[3];
x_[0] = offset;
y_[0] = offset;
x_[1] = size / 2 + offset;
y_[1] = size - 1 + offset;
x_[2] = size - 1 + offset;
y_[2] = offset;
Graphics2D g2D = (Graphics2D) image.getGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
setColor( g2D, fillColor, fillOpacity );
g2D.fillPolygon( x_, y_, 3 );
setColor( g2D, strokeColor, strokeOpacity );
g2D.drawPolygon( x_, y_, 3 );
return image;
}
/**
* Draws a five-pointed star (pentagram) according to the given
* parameters.
* @param size
* resulting image's height and width
* @param fillOpacity
* opacity value for the filled parts of the image
* @param fillColor
* <tt>Color</tt> to be used for the fill
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return an image of a pentagram
*/
public BufferedImage drawStar( int size, double fillOpacity, Color fillColor,
double strokeOpacity, Color strokeColor, float[] dash,
float dashOffset, float width, int cap, int join ) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2D = image.createGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
int s = size / 2;
int x0 = s;
int y0 = s;
double sin36 = Math.sin( Math.toRadians( 36 ) );
double cos36 = Math.cos( Math.toRadians( 36 ) );
double sin18 = Math.sin( Math.toRadians( 18 ) );
double cos18 = Math.cos( Math.toRadians( 18 ) );
int smallRadius = (int) ( s * sin18 / Math.sin( Math.toRadians( 54 ) ) );
int p0X = x0;
int p0Y = y0 - s;
int p1X = x0 + (int) ( smallRadius * sin36 );
int p1Y = y0 - (int) ( smallRadius * cos36 );
int p2X = x0 + (int) ( s * cos18 );
int p2Y = y0 - (int) ( s * sin18 );
int p3X = x0 + (int) ( smallRadius * cos18 );
int p3Y = y0 + (int) ( smallRadius * sin18 );
int p4X = x0 + (int) ( s * sin36 );
int p4Y = y0 + (int) ( s * cos36 );
int p5Y = y0 + smallRadius;
int p6X = x0 - (int) ( s * sin36 );
int p7X = x0 - (int) ( smallRadius * cos18 );
int p8X = x0 - (int) ( s * cos18 );
int p9X = x0 - (int) ( smallRadius * sin36 );
int[] x = new int[] { p0X, p1X, p2X, p3X, p4X, p0X, p6X, p7X, p8X, p9X };
int[] y = new int[] { p0Y, p1Y, p2Y, p3Y, p4Y, p5Y, p4Y, p3Y, p2Y, p1Y };
for ( int i = 0; i < y.length; i++ ) {
x[i] = x[i] + offset;
y[i] = y[i] + offset;
}
Polygon shape = new Polygon( x, y, 10 );
setColor( g2D, fillColor, fillOpacity );
g2D.fill( shape );
setColor( g2D, strokeColor, strokeOpacity );
g2D.draw( shape );
g2D.dispose();
return image;
}
/**
* Draws a scaled instance of a circle mark according to the given
* parameters.
*
* @param size
* resulting image's height and widthh
* @param fillOpacity
* opacity value for the filled parts of the image
* @param fillColor
* <tt>Color</tt> to be used for the fill
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return image displaying a circle
* @throws FilterEvaluationException
*/
public BufferedImage drawCircle( int size, double fillOpacity, Color fillColor,
double strokeOpacity, Color strokeColor, float[] dash,
float dashOffset, float width, int cap, int join ) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2D = (Graphics2D) image.getGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
setColor( g2D, fillColor, fillOpacity );
g2D.fillOval( offset, offset, size, size );
setColor( g2D, strokeColor, strokeOpacity );
g2D.drawOval( offset, offset, size, size );
return image;
}
/**
* Draws a scaled instance of a square mark according to the given
* parameters.
*
* @param size
* resulting image's height and widthh
* @param fillOpacity
* opacity value for the filled parts of the image
* @param fillColor
* <tt>Color</tt> to be used for the fill
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return image displaying a square
*/
public BufferedImage drawSquare( int size, double fillOpacity, Color fillColor,
double strokeOpacity, Color strokeColor, float[] dash,
float dashOffset, float width, int cap, int join ) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2D = (Graphics2D) image.getGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
setColor( g2D, fillColor, fillOpacity );
g2D.fillRect( offset, offset, size, size );
setColor( g2D, strokeColor, strokeOpacity );
g2D.drawRect( offset, offset, size - 1, size - 1 );
return image;
}
/**
* Draws a scaled instance of a cross mark (a "+") according to the given
* parameters.
*
* @param size
* resulting image's height and widthh
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return image displaying a cross (a "+")
*/
public BufferedImage drawCross1( int size, double strokeOpacity, Color strokeColor,
float[] dash, float dashOffset, float width, int cap, int join) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2D = (Graphics2D) image.getGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
setColor( g2D, strokeColor, strokeOpacity );
g2D.drawLine( offset, size / 2 + offset, size - 1 + offset, size / 2 + offset );
g2D.drawLine( size / 2 + offset, offset, size / 2 + offset, size - 1 + offset );
return image;
}
/**
* Draws a scaled instance of a cross mark (an "X") according to the given
* parameters.
*
* @param size
* resulting image's height and widthh
* @param strokeOpacity
* opacity value for the stroked parts of the image
* @param strokeColor
* <tt>Color</tt> to be used for the strokes
* @param dash dash array for rendering boundary line
* @param width of the boundary line
* @param cap of the boundary line
* @param join of the boundary line
* @param dashOffset
*
* @return image displaying a cross (a "X")
*/
public BufferedImage drawCross2( int size, double strokeOpacity, Color strokeColor, float[] dash,
float dashOffset, float width, int cap, int join ) {
int offset = (int)(width*2+1) / 2;
BufferedImage image = new BufferedImage( size + offset * 2, size + offset * 2,
BufferedImage.TYPE_INT_ARGB );
Graphics2D g2D = (Graphics2D) image.getGraphics();
BasicStroke bs = createBasicStroke( dash, dashOffset, width, cap, join );
g2D.setStroke( bs );
setColor( g2D, strokeColor, strokeOpacity );
g2D.drawLine( offset, offset, size - 1 + offset, size - 1 + offset );
g2D.drawLine( offset, size - 1 + offset, size - 1 + offset, offset );
return image;
}
/**
*
* @param size
* @param fillOpacity
* @param fillColor
* @param strokeOpacity
* @param strokeColor
* @param charDesc e.g. CHAR:Times New Roman:45
*/
private BufferedImage drawCharacter( int size, double fillOpacity, Color fillColor,
double strokeOpacity, Color strokeColor, String charDesc ) {
String[] tmp = StringTools.toArray( charDesc, ":", false );
BufferedImage image = new BufferedImage( size, size, BufferedImage.TYPE_INT_ARGB );
Graphics2D g2 = (Graphics2D) image.getGraphics();
setColor( g2, fillColor, fillOpacity );
g2.fillRect( 0, 0, size, size );
java.awt.Font font = new java.awt.Font( tmp[1], java.awt.Font.PLAIN, size );
g2.setFont( font );
FontMetrics fm = g2.getFontMetrics();
char c = (char) Integer.parseInt( tmp[2] );
int w = fm.charWidth( c );
int h = fm.getHeight();
String s = "" + c;
setColor( g2, strokeColor, strokeOpacity );
g2.drawString( s, size / 2 - w / 2, size / 2 + h / 2 - fm.getDescent() );
g2.dispose();
return image;
}
/**
* @param g2D
* @param color
* @param opacity
*/
private void setColor( Graphics2D g2D, 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 );
}
g2D.setColor( color );
}
/**
* exports the content of the Mark as XML formated String
*
* @return xml representation of the Mark
*/
public String exportAsXML() {
StringBuffer sb = new StringBuffer( 1000 );
sb.append( "<Mark>" );
if ( wellKnownName != null && !wellKnownName.equals( "" ) ) {
sb.append( "<WellKnownName>" ).append( wellKnownName );
sb.append( "</WellKnownName>" );
}
if ( fill != null ) {
sb.append( ( (Marshallable) fill ).exportAsXML() );
}
if ( stroke != null ) {
sb.append( ( (Marshallable) stroke ).exportAsXML() );
}
sb.append( "</Mark>" );
return sb.toString();
}
// private void drawUnicode(Graphics2D g2, int x, int y, double rotation,
// double size, String m, Mark mark) {
// int sz = (int)size;
// double fo = mark.getFill().getOpacity();
// double so = mark.getStroke().getOpacity();
//
// java.awt.Font font = new java.awt.Font("sans serif", java.awt.Font.PLAIN,
// sz);
// g2.setFont( font );
// FontMetrics fm = g2.getFontMetrics();
//
// char c = (char)m.charAt(0);
// int w = fm.charWidth(c);
// int h = fm.getHeight();
//
// g2 = setColor( g2, mark.getFill().getFill(), fo );
// g2.fillRect( x-w/2, y-h/2, w, h);
// g2 = setColor( g2, mark.getStroke().getStroke(), so );
//
// String s = "" + c;
// g2.drawString( s, x-w/2, y+h/2-fm.getDescent());
// }
// else {
//
// Mark[] marks = sym.getGraphic().getMarks();
// double rotation = sym.getGraphic().getRotation();
// double size = sym.getGraphic().getSize();
// if (marks != null) {
//
// for (int k = 0; k > marks.length; k++) {
//
// float w = (float)marks[k].getStroke().getWidth();
// g2.setStroke( new BasicStroke( w ) );
//
// if (marks[k].getWellKnownName().equalsIgnoreCase("triangle") ) {
// drawTriangle( g2, x, y, rotation, size, marks[k] );
// }
// else
// if (marks[k].getWellKnownName().equalsIgnoreCase("circle") ) {
// drawCircle( g2, x, y, rotation, size, marks[k] );
// }
// else
// if (marks[k].getWellKnownName().equalsIgnoreCase("square") ) {
// drawSquare( g2, x, y, rotation, size, marks[k] );
// }
// else
// if (marks[k].getWellKnownName().equalsIgnoreCase("cross") ) {
// drawCross1( g2, x, y, rotation, size, marks[k] );
// }
// else
// if (marks[k].getWellKnownName().equalsIgnoreCase("x") ) {
// drawCross2( g2, x, y, rotation, size, marks[k] );
// }
// else
// if (marks[k].getWellKnownName().length() == 0 ) {
// drawSquare( g2, x, y, rotation, size, marks[k] );
// }
// else {
// drawUnicode( g2, x, y, rotation, size,
// marks[k].getWellKnownName(), marks[k] );
// }
// }
// }
// }
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: Mark.java,v $
Revision 1.14 2006/11/24 14:59:27 poth
*** empty log message ***
Revision 1.13 2006/11/24 14:27:16 poth
*** empty log message ***
Revision 1.12 2006/11/22 21:23:37 poth
bug fixes - considering all Stroke parameters drawing well known marks
Revision 1.11 2006/07/29 08:51:12 poth
references to deprecated classes removed
Revision 1.10 2006/07/21 12:09:43 poth
bug fix - setting fill and stroke transparency drawing a star
Revision 1.9 2006/07/20 15:16:19 taddei
implementation of star
Revision 1.8 2006/07/12 14:46:14 poth
comment footer added
********************************************************************** */