/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.legend;
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import org.geotools.data.DataUtilities;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.LiteShape;
import org.geotools.renderer.style.GraphicStyle2D;
import org.geotools.renderer.style.MarkStyle2D;
import org.geotools.renderer.style.SLDStyleFactory;
import org.geotools.renderer.style.Style2D;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer;
import org.geotools.util.NumberRange;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.expression.Expression;
import org.opengis.referencing.operation.MathTransform;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
/**
* This class is used to isolate GeoTools from the specific graphic library
* being used for rendering.
*
* @author Administrateur
*/
public class Drawer {
private GeometryFactory gf = new GeometryFactory();
private Drawer(){
// prevent subclassing
}
/**
* Retrieve the default Drawing implementation.
*
* @return Drawing ready for use
*/
public static Drawer create(){
return new Drawer();
}
/**
* Used to draw a freature directly onto the provided image.
*
* Feature coordintes are in the same coordinates as the image.
*
*
* You may call this method multiple times to draw several features onto the same
* Image (say for glyph creation).
*
*
* @param image Image to render on to
* @param display Needed to create Colors for image
* @param feature Feature to be rendered
* @param style Style to render feature with
*/
public void drawDirect( BufferedImage bi, SimpleFeature feature, Style style ){
drawFeature(bi, feature, style, new AffineTransform() );
}
public void drawDirect(BufferedImage bi, SimpleFeature feature, Rule rule){
AffineTransform worldToScreenTransform = new AffineTransform();
drawFeature(bi, feature, worldToScreenTransform, false, getSymbolizers(rule), null);
}
public void drawFeature(BufferedImage bi, SimpleFeature feature, AffineTransform worldToScreenTransform, boolean drawVertices, MathTransform mt ) {
if (feature == null)
return;
drawFeature(bi, feature, worldToScreenTransform, drawVertices,getSymbolizers(feature), mt);
}
public void drawFeature(BufferedImage bi, SimpleFeature feature, AffineTransform worldToScreenTransform ) {
if (feature == null)
return;
drawFeature(bi, feature, worldToScreenTransform, false,getSymbolizers(feature), null);
}
public void drawFeature(BufferedImage bi, SimpleFeature feature, AffineTransform worldToScreenTransform, Style style ) {
if (feature == null)
return;
drawFeature(bi, feature, worldToScreenTransform, false,getSymbolizers(style), null);
}
public void drawFeature(BufferedImage bi, SimpleFeature feature, Style style, AffineTransform worldToScreenTransform) {
if (feature == null)
return;
drawFeature(bi, feature, worldToScreenTransform, false, getSymbolizers(style), null);
}
Symbolizer[] getSymbolizers(Style style){
List symbs=new ArrayList();
FeatureTypeStyle[] styles=style.getFeatureTypeStyles();
for( int i = 0; i < styles.length; i++ ) {
FeatureTypeStyle fstyle = styles[i];
Rule[] rules=fstyle.getRules();
for( int j = 0; j < rules.length; j++ ) {
Rule rule = rules[j];
symbs.addAll(Arrays.asList(rule.getSymbolizers()));
}
}
return symbs.toArray(new Symbolizer[symbs.size()]);
}
Symbolizer[] getSymbolizers(Rule rule) {
List symbs=new ArrayList();
symbs.addAll(Arrays.asList(rule.getSymbolizers()));
return symbs.toArray(new Symbolizer[symbs.size()]);
}
public static Symbolizer[] getSymbolizers(SimpleFeature feature) {
return getSymbolizers(((Geometry) feature.getDefaultGeometry()).getClass(), Color.RED);
}
public static Symbolizer[] getSymbolizers(Class extends Geometry> type, Color baseColor) {
return getSymbolizers(type, baseColor, true);
}
public static Symbolizer[] getSymbolizers(Class extends Geometry> type, Color baseColor, boolean useTransparency) {
StyleBuilder builder=new StyleBuilder();
Symbolizer[] syms=new Symbolizer[1];
if( LineString.class.isAssignableFrom(type) ||
MultiLineString.class.isAssignableFrom(type) )
syms[0]=builder.createLineSymbolizer(baseColor,2);
if( Point.class.isAssignableFrom(type) ||
MultiPoint.class.isAssignableFrom(type)){
PointSymbolizer point = builder.createPointSymbolizer(builder.createGraphic());
point.getGraphic().getMarks()[0].setSize((Expression) CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints()).literal(10));
point.getGraphic().getMarks()[0].setFill(builder.createFill(baseColor));
syms[0]=point;
}
if( Polygon.class.isAssignableFrom(type) ||
MultiPolygon.class.isAssignableFrom(type)){
syms[0]=builder.createPolygonSymbolizer(builder.createStroke(baseColor,2), builder.createFill(baseColor, useTransparency?.6:1.0));
}
return syms;
}
public void drawFeature(BufferedImage bi, SimpleFeature feature, AffineTransform worldToScreenTransform, boolean drawVertices, Symbolizer[] symbs, MathTransform mt ) {
LiteShape shape = new LiteShape(null, worldToScreenTransform, false);
if( symbs==null )
return;
for( int m = 0; m < symbs.length; m++ ) {
drawFeature(bi, feature, worldToScreenTransform, drawVertices, symbs[m], mt, shape);
}
}
public void drawFeature( BufferedImage bi, SimpleFeature feature, AffineTransform worldToScreenTransform, boolean drawVertices,
Symbolizer symbolizer, MathTransform mathTransform, LiteShape shape) {
Graphics graphics = bi.getGraphics();
if (symbolizer instanceof RasterSymbolizer) {
// TODO
} else {
Geometry g = findGeometry(feature, symbolizer);
if (g == null)
return;
if ( mathTransform!=null ){
try {
g=JTS.transform(g, mathTransform);
} catch (Exception e) {
// do nothing
}
}
shape.setGeometry(g);
paint(bi, feature, shape, symbolizer);
if (drawVertices) {
double averageDistance = 0;
Coordinate[] coords = g.getCoordinates();
java.awt.Point oldP = worldToPixel(coords[0], worldToScreenTransform);
for( int i = 1; i < coords.length; i++ ) {
Coordinate coord = coords[i];
java.awt.Point p = worldToPixel(coord, worldToScreenTransform);
averageDistance += p.distance(oldP) / i;
oldP = p;
}
int pixels = 1;
if (averageDistance > 20)
pixels = 3;
if (averageDistance > 60)
pixels = 5;
if (pixels > 1) {
graphics.setColor(Color.RED);
for( int i = 0; i < coords.length; i++ ) {
Coordinate coord = coords[i];
java.awt.Point p = worldToPixel(coord, worldToScreenTransform);
graphics.fillRect(p.x - (pixels - 1) / 2, p.y - (pixels - 1) / 2,
pixels, pixels);
}
}
}
}
}
/** Unsure if this is the paint for the border, or the fill? */
private void paint( BufferedImage bi, SimpleFeature feature, LiteShape shape, Symbolizer symb ) {
Graphics graphics = bi.getGraphics();
Graphics2D g = (Graphics2D) graphics;
if( symb instanceof PolygonSymbolizer){
PolygonSymbolizer polySymb = (PolygonSymbolizer) symb;
Color stroke=SLD.polyColor(polySymb);
double opacity=SLD.polyFillOpacity(polySymb);
Color fill=SLD.polyFill(polySymb);
int width = SLD.width(SLD.stroke(polySymb));
if(width == SLD.NOTFOUND)
width =1;
if( Double.isNaN(opacity) ) opacity = 1.0;
if( fill != null ){
fill=new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), (int)(255*opacity));
g.setColor( fill );
g.fill( shape );
}
if( stroke != null ){
g.setColor( stroke );
Stroke str = new BasicStroke(width);
g.setStroke(str);
g.draw( shape );
}
}
if( symb instanceof LineSymbolizer){
LineSymbolizer lineSymbolizer = (LineSymbolizer) symb;
Color c = SLD.color( lineSymbolizer );
int w = SLD.width( lineSymbolizer );
if( c != null && w > 0 ){
g.setColor( c );
Stroke str = new BasicStroke(w);
g.setStroke(str);
g.draw( shape );
}
}
if( symb instanceof PointSymbolizer){
PointSymbolizer pointSymbolizer = (PointSymbolizer) symb;
Color c = SLD.pointColor( pointSymbolizer );
Color fill=SLD.pointFill( pointSymbolizer );
int width = SLD.width(SLD.stroke(pointSymbolizer));
if(width == SLD.NOTFOUND)
width =1;
float[] point=new float[6];
shape.getPathIterator(null).currentSegment(point);
SLDStyleFactory styleFactory=new SLDStyleFactory();
Style2D tmp = styleFactory.createStyle(feature, pointSymbolizer, new NumberRange(Double.MIN_VALUE, Double.MAX_VALUE));
if( tmp instanceof MarkStyle2D ){
MarkStyle2D style=(MarkStyle2D) tmp;
Shape shape2 = style.getTransformedShape(point[0], point[1]);
if( c == null && fill == null ){
g.setColor( Color.GRAY );
g.fill( shape2 );
}
if( fill != null ){
g.setColor( fill );
g.fill( shape2 );
} else{
g.setColor( Color.GRAY );
g.fill( shape2 );
}
if( c != null ){
Stroke str = new BasicStroke(width);
g.setStroke(str);
g.setColor( c );
g.draw( shape2 );
}else{
Stroke str = new BasicStroke(width);
g.setStroke(str);
g.setColor( Color.DARK_GRAY );
g.draw( shape2 );
}
}else if( tmp instanceof GraphicStyle2D){
GraphicStyle2D style=(GraphicStyle2D) tmp;
float rotation = style.getRotation();
g.setTransform(AffineTransform.getRotateInstance(rotation));
BufferedImage image = (BufferedImage)style.getImage();
g.drawImage(image, (int)(point[0]-((double)image.getWidth())/(double)2), (int)(point[1]-((double)image.getHeight())/(double)2),null);
}
}
}
/**
* Finds the geometric attribute requested by the symbolizer
*
* @param f The victim
* @param s The symbolizer
* @param style the resolved style for the specified victim
* @return The geometry requested in the symbolizer, or the default geometry if none is
* specified
*/
private com.vividsolutions.jts.geom.Geometry findGeometry( SimpleFeature f, Symbolizer s) {
String geomName = getGeometryPropertyName(s);
// get the geometry
com.vividsolutions.jts.geom.Geometry geom;
if (geomName == null) {
geom = (com.vividsolutions.jts.geom.Geometry) f.getDefaultGeometry();
} else {
geom = (com.vividsolutions.jts.geom.Geometry) f.getAttribute(geomName);
}
// if the symbolizer is a point or text symbolizer generate a suitable
// location to place the
// point in order to avoid recomputing that location at each rendering
// step
if ((s instanceof PointSymbolizer || s instanceof TextSymbolizer)
&& !(geom instanceof Point)) {
if (geom instanceof LineString && !(geom instanceof LinearRing)) {
// use the mid point to represent the point/text symbolizer
// anchor
Coordinate[] coordinates = geom.getCoordinates();
Coordinate start = coordinates[0];
Coordinate end = coordinates[1];
Coordinate mid = new Coordinate((start.x + end.x) / 2, (start.y + end.y) / 2);
geom = geom.getFactory().createPoint(mid);
} else {
// otherwise use the centroid of the polygon
geom = geom.getCentroid();
}
}
return geom;
}
private String getGeometryPropertyName( Symbolizer s ) {
String geomName = null;
// TODO: fix the styles, the getGeometryPropertyName should probably be
// moved into an interface...
if (s instanceof PolygonSymbolizer) {
geomName = ((PolygonSymbolizer) s).getGeometryPropertyName();
} else if (s instanceof PointSymbolizer) {
geomName = ((PointSymbolizer) s).getGeometryPropertyName();
} else if (s instanceof LineSymbolizer) {
geomName = ((LineSymbolizer) s).getGeometryPropertyName();
} else if (s instanceof TextSymbolizer) {
geomName = ((TextSymbolizer) s).getGeometryPropertyName();
}
return geomName;
}
public java.awt.Point worldToPixel( Coordinate coord, AffineTransform worldToScreenTransform ) {
Point2D w = new Point2D.Double(coord.x, coord.y);
AffineTransform at = worldToScreenTransform;
Point2D p = at.transform(w, new Point2D.Double());
return new java.awt.Point((int) p.getX(), (int) p.getY());
}
/**
* TODO summary sentence for worldToScreenTransform ...
*
* @param bounds
* @param rectangle
* @return
*/
public static AffineTransform worldToScreenTransform( Envelope mapExtent, Rectangle screenSize ) {
double scaleX = screenSize.getWidth() / mapExtent.getWidth();
double scaleY = screenSize.getHeight() / mapExtent.getHeight();
double tx = -mapExtent.getMinX() * scaleX;
double ty = (mapExtent.getMinY() * scaleY) + screenSize.getHeight();
AffineTransform at = new AffineTransform(scaleX, 0.0d, 0.0d, -scaleY, tx, ty);
return at;
}
/**
* Create a SimpleFeatureType schema using a type short hand.
*
* Code Example:
* new Drawing().schema("namespace.typename", "id:0,*geom:LineString,name:String,*centroid:Point");
*
*
* - SimpleFeatureType with identifier "namespace.typename"
*
- Default Geometry "geom" of type LineStirng indicated with a "*"
*
- Three attributes: id of type Integer, name of type String and centroid of type Point
*
*
* @param name namespace.name
* @param spec
* @return Generated SimpleFeatureType
*/
public SimpleFeatureType schema( String name, String spec ){
try {
return DataUtilities.createType( name, spec );
} catch (SchemaException e) {
throw new IllegalArgumentException( e );
}
}
static SimpleFeatureType pointSchema;
static SimpleFeatureType lineSchema ;
static SimpleFeatureType polygonSchema ;
static SimpleFeatureType multipointSchema ;
static SimpleFeatureType multilineSchema ;
static SimpleFeatureType multipolygonSchema ;
static {
try {
pointSchema = DataUtilities.createType( "generated:point", "*point:Point" ); //$NON-NLS-1$ //$NON-NLS-2$
lineSchema = DataUtilities.createType( "generated:linestring", "*linestring:LineString" ); //$NON-NLS-1$ //$NON-NLS-2$
polygonSchema = DataUtilities.createType( "generated:polygon", "*polygon:Polygon" ); //$NON-NLS-1$ //$NON-NLS-2$
multipointSchema = DataUtilities.createType( "generated:multipoint", "*multipoint:MultiPoint" ); //$NON-NLS-1$ //$NON-NLS-2$
multilineSchema = DataUtilities.createType( "generated:multilinestring", "*multilinestring:MultiLineString" ); //$NON-NLS-1$ //$NON-NLS-2$
multipolygonSchema = DataUtilities.createType( "generated:multipolygon", "*multipolygon:MultiPolygon" ); //$NON-NLS-1$ //$NON-NLS-2$
} catch ( SchemaException unExpected ){
System.err.println( unExpected );
}
}
/**
* Just a convinient method to create feature from geometry.
*
* @param geom the geometry to create feature from
* @return feature instance
*/
public SimpleFeature feature(Geometry geom){
if(geom instanceof Polygon){
return feature((Polygon)geom);
}else if(geom instanceof MultiPolygon){
return feature((MultiPolygon)geom);
}else if(geom instanceof Point){
return feature((Point)geom);
}else if(geom instanceof LineString){
return feature((LineString)geom);
}else if(geom instanceof MultiPoint){
return feature((MultiPoint)geom);
}else if(geom instanceof MultiLineString){
return feature((MultiLineString)geom);
}else{
throw new IllegalArgumentException("Geometry is not supported to create feature"); //$NON-NLS-1$
}
}
/**
* Simple feature with one attribute called "point".
* @param point
* @return SimpleFeature with a default geometry and no attribtues
*/
public SimpleFeature feature( Point point ) {
if( point == null ) throw new NullPointerException("Point required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(pointSchema, new Object[]{ point }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+point ); //$NON-NLS-1$
}
}
/**
* Simple Feature with a default geometry and no attribtues.
* @param line
* @return Feature with a default geometry and no attribtues
*/
public SimpleFeature feature( LineString line ) {
if( line == null ) throw new NullPointerException("line required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(lineSchema, new Object[]{ line }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+line ); //$NON-NLS-1$
}
}
/**
* Simple Feature with a default geometry and no attribtues.
* @param polygon
* @return Feature with a default geometry and no attribtues
*/
public SimpleFeature feature( Polygon polygon ) {
if( polygon == null ) throw new NullPointerException("polygon required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(polygonSchema, new Object[]{ polygon }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+polygon ); //$NON-NLS-1$
}
}
/**
* Simple Feature with a default geometry and no attribtues.
* @param multipoint
* @return Feature with a default geometry and no attribtues
*/
public SimpleFeature feature( MultiPoint multipoint ) {
if( multipoint == null ) throw new NullPointerException("multipoint required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(multipointSchema, new Object[]{ multipoint }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+multipoint ); //$NON-NLS-1$
}
}
/**
* Simple Feature with a default geometry and no attribtues.
* @param multilinestring
* @return Feature with a default geometry and no attribtues
*/
public SimpleFeature feature( MultiLineString multilinestring ) {
if( multilinestring == null ) throw new NullPointerException("multilinestring required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(multilineSchema, new Object[]{ multilinestring }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+multilinestring ); //$NON-NLS-1$
}
}
/**
* Simple Feature with a default geometry and no attribtues.
* @param multipolygon
* @return Feature with a default geometry and no attribtues
*/
public SimpleFeature feature( MultiPolygon multipolygon ) {
if( multipolygon == null ) throw new NullPointerException("multipolygon required"); //$NON-NLS-1$
try {
return SimpleFeatureBuilder.build(multipolygonSchema, new Object[]{ multipolygon }, null);
} catch (IllegalAttributeException e) {
// this should not happen because we *know* the parameter matches schame
throw new RuntimeException("Could not generate feature for point "+multipolygon ); //$NON-NLS-1$
}
}
/**
* Generate Point from two dimensional ordinates
*
* @param x
* @param y
* @return Point
*/
public Point point( int x, int y ){
return gf.createPoint( new Coordinate( x, y ) );
}
/**
* Generate LineStrings from two dimensional ordinates
*
* @param xy
* @return LineStirng
*/
public LineString line(int[] xy) {
Coordinate[] coords = new Coordinate[xy.length / 2];
for (int i = 0; i < xy.length; i += 2) {
coords[i / 2] = new Coordinate(xy[i], xy[i + 1]);
}
return gf.createLineString(coords);
}
/**
* Generate a MultiLineString from two dimensional ordinates
*
* @param xy
* @return MultiLineStirng
*/
public MultiLineString lines(int[][] xy) {
LineString[] lines = new LineString[xy.length];
for (int i = 0; i < xy.length; i++) {
lines[i] = line(xy[i]);
}
return gf.createMultiLineString(lines);
}
/**
* Convience constructor for GeometryFactory.createPolygon.
*
* The provided xy ordinates are turned into a linear rings.
*
* @param xy Two dimensional ordiantes.
* @return Polygon
*/
public Polygon polygon( int[] xy ){
LinearRing shell = ring( xy );
return gf.createPolygon( shell, null );
}
/**
* Convience constructor for GeometryFactory.createPolygon.
*
* The provided xy and holes are turned into linear rings.
*
* @param xy Two dimensional ordiantes.
* @param holes Holes in polygon or null.
*
* @return Polygon
*/
public Polygon polygon( int[] xy, int []holes[] ){
if( holes == null || holes.length == 0){
return polygon( xy );
}
LinearRing shell = ring( xy );
LinearRing[] rings = new LinearRing[holes.length];
for (int i = 0; i < xy.length; i++) {
rings[i] = ring(holes[i]);
}
return gf.createPolygon( shell, rings );
}
/**
* Convience constructor for GeometryFactory.createLinearRing.
*
* @param xy Two dimensional ordiantes.
* @return LinearRing for use with polygon
*/
public LinearRing ring( int[] xy ){
int length = xy.length / 2;
if( xy[0] != xy[xy.length-2] || xy[1] != xy[xy.length-1]){
length++;
}
Coordinate[] coords = new Coordinate[length];
for (int i = 0; i < xy.length; i += 2) {
coords[i / 2] = new Coordinate(xy[i], xy[i + 1]);
}
if( xy[0] != xy[xy.length-2] || xy[1] != xy[xy.length-1]){
coords[length-1] = coords[0];
}
return gf.createLinearRing(coords);
}
}