/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2016, 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.renderer.lite;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.RenderingHints.Key;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import org.geotools.geometry.jts.Decimator;
import org.geotools.geometry.jts.LiteShape2;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.renderer.style.GraphicStyle2D;
import org.geotools.renderer.style.IconStyle2D;
import org.geotools.renderer.style.LineStyle2D;
import org.geotools.renderer.style.MarkStyle2D;
import org.geotools.renderer.style.PointStyle2D;
import org.geotools.renderer.style.PolygonStyle2D;
import org.geotools.renderer.style.Style2D;
import org.opengis.filter.expression.Literal;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.TransformException;
import org.opengis.style.ExternalGraphic;
import org.opengis.style.GraphicLegend;
import org.opengis.style.GraphicalSymbol;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
/**
* A simple class that knows how to paint a Shape object onto a Graphic given a
* Style2D. It's the last step of the rendering engine, and has been factored
* out since both renderers do use the same painting logic.
*
* @author Andrea Aime
*
*
* @source $URL$
*/
public class StyledShapePainter {
public final static Key TEXTURE_ANCHOR_HINT_KEY = new TextureAnchorKey();
private final static AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
/** The logger for the rendering module. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(StyledShapePainter.class.getName());
/**
* Whether icon centers should be matched to a pixel center, or not
*/
public static boolean ROUND_ICON_COORDS = Boolean.parseBoolean(System.getProperty("org.geotools.renderer.lite.roundIconCoords", "true"));
/**
* Whether to apply the new vector hatch fill optimization, or not (on by default, this is just a safeguard)
*/
public static boolean OPTIMIZE_VECTOR_HATCH_FILLS = Boolean.parseBoolean(System.getProperty("org.geotools.renderer.lite.optimizeVectorHatchFills", "true"));
/**
* the label cache, used to populate the label cache with reserved areas for labeling
* obstacles
*/
LabelCache labelCache;
public StyledShapePainter() {
// nothing do do, just needs to exist
}
public StyledShapePainter(LabelCache cache) {
this.labelCache = cache;
}
public void paint(final Graphics2D graphics, final LiteShape2 shape,
final Style2D style, final double scale) {
paint(graphics, shape, style, scale, false);
}
/**
* Invoked automatically when a polyline is about to be draw. This
* implementation paints the polyline according to the rendered style
*
* @param graphics
* The graphics in which to draw.
* @param shape
* The polygon to draw.
* @param style
* The style to apply, or <code>null</code> if none.
* @param scale
* The scale denominator for the current zoom level
* @throws FactoryException
* @throws TransformException
*/
public void paint(final Graphics2D graphics, final LiteShape2 shape,
final Style2D style, final double scale, boolean isLabelObstacle) {
if (style == null) {
// TODO: what's going on? Should not be reached...
LOGGER.severe("ShapePainter has been asked to paint a null style!!");
return;
}
// Is the current scale within the style scale range?
if (!style.isScaleInRange(scale)) {
LOGGER.fine("Out of scale");
return;
}
if(style instanceof IconStyle2D) {
AffineTransform temp = graphics.getTransform();
try {
IconStyle2D icoStyle = (IconStyle2D) style;
Icon icon = icoStyle.getIcon();
graphics.setComposite(icoStyle.getComposite());
// the displacement to be applied to all points, centers the icon and applies the
// Graphic displacement as well
float dx = icoStyle.getDisplacementX();
float dy = icoStyle.getDisplacementY();
// iterate over all points
float[] coords = new float[2];
PathIterator citer = getPathIterator(shape);
AffineTransform at = new AffineTransform(temp);
while (!(citer.isDone())) {
if (citer.currentSegment(coords) != PathIterator.SEG_MOVETO) {
at.setTransform(temp);
double x = coords[0] + dx;
double y = coords[1] + dy;
at.translate(x, y);
at.rotate(icoStyle.getRotation());
at.translate(-(icon.getIconWidth() * icoStyle.getAnchorPointX()),
(icon.getIconHeight() * (icoStyle.getAnchorPointY() - 1)));
graphics.setTransform(at);
icon.paintIcon(null, graphics, 0, 0);
if (isLabelObstacle) {
// TODO: rotation?
labelCache.put(new Rectangle2D.Double(x, y, icon.getIconWidth(), icon
.getIconHeight()));
}
}
citer.next();
}
} finally {
graphics.setTransform(temp);
}
} else if (style instanceof MarkStyle2D) {
PathIterator citer = getPathIterator(shape);
// get the point onto the shape has to be painted
float[] coords = new float[2];
MarkStyle2D ms2d = (MarkStyle2D) style;
Shape transformedShape ;
while (!(citer.isDone())) {
if (citer.currentSegment(coords) != PathIterator.SEG_MOVETO) {
transformedShape = ms2d.getTransformedShape(coords[0], coords[1]);
if (transformedShape != null) {
if (ms2d.getFill() != null) {
graphics.setPaint(ms2d.getFill());
graphics.setComposite(ms2d.getFillComposite());
graphics.fill(transformedShape);
}
if (ms2d.getContour() != null) {
graphics.setPaint(ms2d.getContour());
graphics.setStroke(ms2d.getStroke());
graphics.setComposite(ms2d.getContourComposite());
graphics.draw(transformedShape);
}
if (isLabelObstacle) {
labelCache.put(transformedShape.getBounds2D());
}
}
}
citer.next();
}
} else if (style instanceof GraphicStyle2D) {
float[] coords = new float[2];
PathIterator iter = getPathIterator(shape);
iter.currentSegment(coords);
GraphicStyle2D gs2d = (GraphicStyle2D) style;
BufferedImage image = gs2d.getImage();
double dx = gs2d.getDisplacementX() - gs2d.getAnchorPointX() * image.getWidth();
double dy = gs2d.getDisplacementY() - ((1 - gs2d.getAnchorPointY()) * image.getHeight());
while (!(iter.isDone())) {
if (iter.currentSegment(coords) != PathIterator.SEG_MOVETO) {
renderImage(graphics, coords[0], coords[1], dx, dy, image, gs2d.getRotation(),
gs2d.getComposite(), isLabelObstacle);
}
iter.next();
}
} else {
if (isLabelObstacle) {
labelCache.put(shape.getBounds2D());
}
// if the style is a polygon one, process it even if the polyline is
// not closed (by SLD specification)
if (style instanceof PolygonStyle2D && !optimizeOutFill((PolygonStyle2D) style, shape)) {
PolygonStyle2D ps2d = (PolygonStyle2D) style;
if (ps2d.getFill() != null) {
Paint paint = ps2d.getFill();
if (paint instanceof TexturePaint) {
TexturePaint tp = (TexturePaint) paint;
BufferedImage image = tp.getImage();
Rectangle2D cornerRect = tp.getAnchorRect();
Point2D anchorPoint = (Point2D) graphics
.getRenderingHint(TEXTURE_ANCHOR_HINT_KEY);
Rectangle2D alignedRect = null;
if (anchorPoint != null) {
alignedRect = new Rectangle2D.Double(Math.round(anchorPoint.getX()),
Math.round(anchorPoint.getY()), cornerRect.getWidth(),
cornerRect.getHeight());
} else {
alignedRect = new Rectangle2D.Double(0.0, 0.0, cornerRect.getWidth(),
cornerRect.getHeight());
}
paint = new TexturePaint(image, alignedRect);
}
graphics.setPaint(paint);
graphics.setComposite(ps2d.getFillComposite());
fillLiteShape(graphics, shape);
}
if (ps2d.getGraphicFill() != null) {
Shape oldClip = graphics.getClip();
try {
paintGraphicFill(graphics, shape, ps2d.getGraphicFill(), scale);
} finally {
graphics.setClip(oldClip);
}
}
}
if (style instanceof LineStyle2D) {
LineStyle2D ls2d = (LineStyle2D) style;
paintLineStyle(graphics, shape, ls2d, isLabelObstacle, 0.5f);
}
}
}
/**
* Checks if the fill can simply be omitted because it's not going to be visible
* anyways. It takes a style that has a solid outline and a width or height that's
* less than the stroke width
* @param style
* @param shape
* @return
*/
private boolean optimizeOutFill(PolygonStyle2D style, LiteShape2 shape) {
// if we have a graphic stroke the outline might not be solid, so, not covering
if(style.getGraphicStroke() != null) {
return false;
}
final Stroke stroke = style.getStroke();
if(stroke == null || !(stroke instanceof BasicStroke)) {
return false;
}
// we need a solid composite to optimize out
Composite composite = style.getContourComposite();
if(!(composite instanceof AlphaComposite)) {
return false;
}
AlphaComposite ac = (AlphaComposite) composite;
if(ac.getAlpha() < 1) {
return false;
}
// if dashed, it's not covering
BasicStroke basic = (BasicStroke) stroke;
if(basic.getDashArray() != null) {
return false;
}
float lineWidth = basic.getLineWidth();
Rectangle2D bounds = shape.getBounds2D();
return bounds.getWidth() < lineWidth || bounds.getHeight() < lineWidth;
}
void paintLineStyle(final Graphics2D graphics, final LiteShape2 shape,
final LineStyle2D ls2d, boolean isLabelObstacle, float strokeWidthAdjustment) {
if (ls2d.getStroke() != null) {
// see if a graphic stroke is to be used, the drawing method
// is completely
// different in this case
if (ls2d.getGraphicStroke() != null) {
drawWithGraphicsStroke(graphics, dashShape(shape, ls2d.getStroke()), ls2d.getGraphicStroke(), isLabelObstacle);
} else {
Paint paint = ls2d.getContour();
if (paint instanceof TexturePaint) {
TexturePaint tp = (TexturePaint) paint;
BufferedImage image = tp.getImage();
Rectangle2D rect = tp.getAnchorRect();
AffineTransform at = graphics.getTransform();
double width = rect.getWidth() * at.getScaleX();
double height = rect.getHeight() * at.getScaleY();
Rectangle2D scaledRect = new Rectangle2D.Double(0,
0, width, height);
paint = new TexturePaint(image, scaledRect);
}
// debugShape(shape);
Stroke stroke = ls2d.getStroke();
if (graphics
.getRenderingHint(RenderingHints.KEY_ANTIALIASING) == RenderingHints.VALUE_ANTIALIAS_ON) {
if (stroke instanceof BasicStroke && strokeWidthAdjustment > 0) {
BasicStroke bs = (BasicStroke) stroke;
stroke = new BasicStroke(
bs.getLineWidth() + strokeWidthAdjustment, bs
.getEndCap(), bs.getLineJoin(),
bs.getMiterLimit(), bs.getDashArray(),
bs.getDashPhase());
}
}
graphics.setPaint(paint);
graphics.setStroke(stroke);
graphics.setComposite(ls2d.getContourComposite());
graphics.draw(shape);
}
}
}
/**
* Paints a GraphicLegend in the supplied graphics
*
* @param graphics
* The graphics in which to draw.
* @param shape
* The shape to draw.
* @param legend
* The legend to apply.
* @param symbolScale
* The scale of the symbol, if the legend graphic has to be rescaled
*/
public void paint(final Graphics2D graphics, final LiteShape2 shape, final GraphicLegend legend,
final double symbolScale, boolean isLabelObstacle) {
if (legend == null) {
// TODO: what's going on? Should not be reached...
throw new NullPointerException("ShapePainter has been asked to paint a null legend!!");
}
Iterator<GraphicalSymbol> symbolIter = legend.graphicalSymbols().iterator();
while(symbolIter.hasNext()) {
GraphicalSymbol symbol = symbolIter.next();
if (symbol instanceof ExternalGraphic) {
float[] coords = new float[2];
PathIterator iter = getPathIterator(shape);
iter.currentSegment(coords);
// Note: Converting to Radians here due to direct use of SLD Expressions which uses degrees
double rotation = Math.toRadians( ((Literal)legend.getRotation()).evaluate(null, Double.class));
float opacity = ((Literal)legend.getOpacity()).evaluate(null, Float.class);
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
opacity);
ExternalGraphic graphic = (ExternalGraphic) symbol;
while (!(iter.isDone())) {
iter.currentSegment(coords);
try {
BufferedImage image = ImageIO.read(graphic.getOnlineResource().getLinkage().toURL());
if ((symbolScale > 0.0) && (symbolScale != 1.0)) {
int w = (int) (image.getWidth() / symbolScale);
int h = (int) (image.getHeight() / symbolScale);
int imageType = image.getType() == 0 ? BufferedImage.TYPE_4BYTE_ABGR : image.getType();
BufferedImage rescaled = new BufferedImage(w, h, imageType);
Graphics2D g = rescaled.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(image, 0, 0, w, h, 0, 0, image.getWidth(), image.getHeight(), null);
g.dispose();
image = rescaled;
}
renderImage(graphics, coords[0], coords[1], -image.getWidth() / 2.0,
-image.getHeight() / 2.0,
// Doesn't seem to work with SVGs
// Looking at the SLDStyleFactory, they get the icon from an
// ExternalGraphicFactory.
image, rotation, composite, isLabelObstacle);
} catch (IOException ex) {
Logger.getLogger(StyledShapePainter.class.getName()).log(Level.SEVERE, null, ex);
}
iter.next();
}
}
}
}
Shape dashShape(Shape shape, Stroke stroke) {
if(!(stroke instanceof BasicStroke)) {
return shape;
}
BasicStroke bs = (BasicStroke) stroke;
if(bs.getDashArray() == null || bs.getDashArray().length == 0) {
return shape;
}
return new DashedShape(shape, bs.getDashArray(), bs.getDashPhase());
}
/**
* Extracts a ath iterator from the shape
*
* @param shape
* @return
*/
private PathIterator getPathIterator(final LiteShape2 shape) {
return shape.getPathIterator(IDENTITY_TRANSFORM);
}
void debugShape(Shape shape) {
float[] pt = new float[2];
PathIterator iter = shape.getPathIterator(null);
while (!(iter.isDone())) {
int type = iter.currentSegment(pt);
String event = "unknown";
if (type == PathIterator.SEG_CLOSE)
event = "SEG_CLOSE";
if (type == PathIterator.SEG_CUBICTO)
event = "SEG_CUBIC";
if (type == PathIterator.SEG_LINETO)
event = "SEG_LINETO";
if (type == PathIterator.SEG_MOVETO)
event = "SEG_MOVETO";
if (type == PathIterator.SEG_QUADTO)
event = "SEG_QUADTO";
System.out.println(event + " " + pt[0] + "," + pt[1]);
iter.next();
}
}
// draws the image along the path
private void drawWithGraphicsStroke(Graphics2D graphics, Shape shape, Style2D graphicStroke,
boolean isLabelObstacle) {
PathIterator pi = shape.getPathIterator(null);
double[] coords = new double[4];
int type;
// I suppose the image has been already scaled and its square
double imageSize;
double graphicRotation = 0; // rotation in radians
if(graphicStroke instanceof MarkStyle2D) {
imageSize = ((MarkStyle2D) graphicStroke).getSize();
graphicRotation = ((MarkStyle2D) graphicStroke).getRotation();
} else if(graphicStroke instanceof IconStyle2D) {
imageSize = ((IconStyle2D) graphicStroke).getIcon().getIconWidth();
graphicRotation = ((IconStyle2D) graphicStroke).getRotation();
} else {
GraphicStyle2D gs = (GraphicStyle2D) graphicStroke;
imageSize = gs.getImage().getWidth() - gs.getBorder();
graphicRotation = ((GraphicStyle2D) graphicStroke).getRotation();
}
Composite composite = ((PointStyle2D) graphicStroke).getComposite();
if (composite == null) {
composite = AlphaComposite.SrcOver;
}
double[] first = new double[2];
double[] previous = new double[2];
type = pi.currentSegment(coords);
first[0] = coords[0];
first[1] = coords[1];
previous[0] = coords[0];
previous[1] = coords[1];
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("starting at " + first[0] + "," + first[1]);
}
pi.next();
double remainder, dx, dy, len;
remainder = imageSize / 2.0;
while (!pi.isDone()) {
type = pi.currentSegment(coords);
switch (type) {
case PathIterator.SEG_MOVETO:
// nothing to do?
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("moving to " + coords[0] + "," + coords[1]);
}
first[0] = coords[0];
first[1] = coords[1];
remainder = imageSize / 2.0;
break;
case PathIterator.SEG_CLOSE:
// draw back to first from previous
coords[0] = first[0];
coords[1] = first[1];
remainder = imageSize / 2.0;
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("closing from " + previous[0] + ","
+ previous[1] + " to " + coords[0] + ","
+ coords[1]);
}
// no break here - fall through to next section
case PathIterator.SEG_LINETO:
// draw from previous to coords
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("drawing from " + previous[0] + ","
+ previous[1] + " to " + coords[0] + ","
+ coords[1]);
}
dx = coords[0] - previous[0];
dy = coords[1] - previous[1];
len = Math.sqrt((dx * dx) + (dy * dy)); // - imageWidth;
if(len < remainder) {
remainder -= len;
} else {
double theta = Math.atan2(dx, dy);
dx = (Math.sin(theta) * imageSize);
dy = (Math.cos(theta) * imageSize);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("dx = " + dx + " dy " + dy + " step = "
+ Math.sqrt((dx * dx) + (dy * dy)));
}
double rotation = -(theta - (Math.PI / 2d));
double x = previous[0] + (Math.sin(theta) * remainder);
double y = previous[1] + (Math.cos(theta) * remainder);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("len =" + len + " imageSize " + imageSize);
}
double dist = 0;
for (dist = remainder; dist < len; dist += imageSize) {
renderGraphicsStroke(graphics, x, y, graphicStroke, rotation,
graphicRotation, composite, isLabelObstacle);
x += dx;
y += dy;
}
remainder = dist - len;
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("loop end dist " + dist + " len " + len + " "
+ (len - dist));
}
}
break;
default:
LOGGER
.warning("default branch reached in drawWithGraphicStroke");
}
previous[0] = coords[0];
previous[1] = coords[1];
pi.next();
}
}
/**
* Renders an image on the device
*
* @param graphics the image location on the screen, x coordinate
* @param x the image location on the screen, y coordinate
* @param y the image
* @param dx TODO
* @param dy TODO
* @param image image to draw
* @param rotation the image rotation in radians
* @param composite the alpha blending/composition operator
*/
private void renderImage(Graphics2D graphics, double x, double y, double dx, double dy,
BufferedImage image, double rotation, Composite composite, boolean isLabelObstacle) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("drawing Image @" + x + "," + y);
}
AffineTransform markAT = new AffineTransform();
if(ROUND_ICON_COORDS && rotation == 0) {
// this results in sharper images to be painted
long tx = Math.round(x + dx);
long ty = Math.round(y + dy);
markAT.translate(tx, ty);
} else {
markAT.translate(x, y);
markAT.rotate(rotation);
markAT.translate(dx, dy);
}
if (isLabelObstacle) {
int w = Math.max(image.getWidth() * 1, 1);
int h = Math.max(image.getHeight() * 1, 1);
labelCache.put(new Rectangle2D.Double(x + dx, y + dy, w, h));
}
graphics.setComposite(composite);
Object interpolation = graphics
.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
if (interpolation == null) {
interpolation = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
}
try {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawRenderedImage(image, markAT);
} finally {
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
interpolation);
}
}
private void renderGraphicsStroke(Graphics2D graphics, double x, double y, Style2D style,
double rotation, double graphicRotation, Composite composite, boolean isLabelObstacle) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("drawing GraphicsStroke@" + x + "," + y);
}
graphics.setComposite(composite);
if(style instanceof GraphicStyle2D) {
GraphicStyle2D gstyle = (GraphicStyle2D) style;
BufferedImage image = gstyle.getImage();
double dx = -image.getWidth() * gstyle.getAnchorPointX() + gstyle.getDisplacementX();
double dy = -image.getHeight() * gstyle.getAnchorPointY() + gstyle.getDisplacementY();
renderImage(graphics, x, y, dx, dy, image, rotation, composite, isLabelObstacle);
} else if(style instanceof MarkStyle2D) {
// almost like the code in the main paint method, but
// here we don't use the mark composite
MarkStyle2D ms2d = (MarkStyle2D) style;
Shape transformedShape = ms2d.getTransformedShape((float) x, (float) y,
(float) rotation, (float) graphicRotation);
if (transformedShape != null) {
if (ms2d.getFill() != null) {
graphics.setPaint(ms2d.getFill());
graphics.fill(transformedShape);
}
if (ms2d.getContour() != null) {
graphics.setPaint(ms2d.getContour());
graphics.setStroke(ms2d.getStroke());
graphics.draw(transformedShape);
}
if (isLabelObstacle) {
labelCache.put(transformedShape.getBounds2D());
}
}
} else if(style instanceof IconStyle2D) {
IconStyle2D icons = (IconStyle2D) style;
Icon icon = icons.getIcon();
AffineTransform markAT = new AffineTransform(graphics.getTransform());
markAT.translate(x, y);
markAT.rotate(rotation);
// the displacement to be applied to all points, centers the icon and applies the
// Graphic displacement as well
float dx = -(icon.getIconWidth() * icons.getAnchorPointX()) + icons.getDisplacementX();
float dy = -(icon.getIconHeight() * icons.getAnchorPointY()) + icons.getDisplacementY();
markAT.translate(dx, dy);
AffineTransform temp = graphics.getTransform();
try {
graphics.setTransform(markAT);
icon.paintIcon(null, graphics, 0, 0);
} finally {
graphics.setTransform(temp);
}
if (isLabelObstacle) {
labelCache.put(new Rectangle2D.Double(x+dx,y+dy,icon.getIconWidth(),icon.getIconHeight()));
}
}
}
/**
* Filling multipolygons might result in holes where two polygons overlap. In this method we
* work around that by drawing each polygon as a separate shape
* @param g
* @param shape
*/
void fillLiteShape(Graphics2D g, LiteShape2 shape) {
if(shape.getGeometry() instanceof MultiPolygon && shape.getGeometry().getNumGeometries() > 1) {
MultiPolygon mp = (MultiPolygon) shape.getGeometry();
for (int i = 0; i < mp.getNumGeometries(); i++) {
Polygon p = (Polygon) mp.getGeometryN(i);
try {
g.fill(new LiteShape2(p, null, null, false, false));
} catch(Exception e) {
// should not really happen, but anyways
throw new RuntimeException("Unexpected error occurred while rendering a multipolygon", e);
}
}
} else {
g.fill(shape);
}
}
/**
* Paints a graphic fill for a given shape.
*
* @param graphics Graphics2D on which to paint.
* @param shape Shape whose fill is to be painted.
* @param graphicFill a Style2D that specified the graphic fill.
* @param scale the scale of the current render.
* @throws TransformException
* @throws FactoryException
*/
protected void paintGraphicFill(Graphics2D graphics, Shape shape, Style2D graphicFill, double scale)
{
// retrieves the bounds of the provided shape
Rectangle2D boundsShape = shape.getBounds2D();
// retrieves the size of the stipple to be painted based on the provided graphic fill
Rectangle2D stippleSize = null;
if (graphicFill instanceof MarkStyle2D)
{
final MarkStyle2D ms2d = (MarkStyle2D) graphicFill;
final Shape markShape = ms2d.getShape();
double size = ms2d.getSize();
Rectangle2D boundsFill = markShape.getBounds2D();
double aspect = (boundsFill.getHeight() > 0 && boundsFill.getWidth() > 0) ? boundsFill.getWidth() / boundsFill.getHeight() : 1.0;
stippleSize = new Rectangle2D.Double(0, 0, size * aspect, size);
double scaleFactor = size;
if(boundsFill.getHeight() > 0) {
scaleFactor = size / boundsFill.getHeight();
}
if(OPTIMIZE_VECTOR_HATCH_FILLS) {
final Shape rescaledStipple = AffineTransform.getScaleInstance(scaleFactor, scaleFactor).createTransformedShape(markShape);
ParallelLinesFiller filler = ParallelLinesFiller.fromStipple(rescaledStipple);
if(filler != null) {
Graphics2D clippedGraphics = (Graphics2D)graphics.create();
// adds the provided shape to the Graphics current clip region
clippedGraphics.clip(shape);
LineStyle2D lineStyle = new LineStyle2D();
lineStyle.setStroke(ms2d.getStroke());
lineStyle.setContour(ms2d.getContour());
lineStyle.setContourComposite(ms2d.getContourComposite());
lineStyle.setGraphicStroke(ms2d.getGraphicStroke());
filler.fillRectangle(shape.getBounds2D(), this, clippedGraphics, lineStyle);
return;
}
}
} else if(graphicFill instanceof IconStyle2D) {
Icon icon = ((IconStyle2D)graphicFill).getIcon();
stippleSize = new Rectangle2D.Double(0, 0, icon.getIconWidth(), icon.getIconHeight());
} else if(graphicFill instanceof GraphicStyle2D) {
BufferedImage image = ((GraphicStyle2D) graphicFill).getImage();
stippleSize = new Rectangle2D.Double(0, 0, image.getWidth(), image.getHeight());
} else {
// if graphic fill does not provide bounds information, it is considered
// to be unsupported for stipple painting
return;
}
// computes the number of times the graphic will be painted as a stipple
int toX = (int) Math.ceil(boundsShape.getWidth() / stippleSize.getWidth());
int toY = (int) Math.ceil(boundsShape.getHeight() / stippleSize.getHeight());
// creates a copy of the Graphics so that we can change it freely
Graphics2D g = (Graphics2D)graphics.create();
// adds the provided shape to the Graphics current clip region
g.clip(shape);
// retrieves the full clip region
Shape clipShape = g.getClip();
Rectangle2D boundsClip = clipShape.getBounds2D();
// adjust the iteration indexes to avoid iterating a lot over areas that we won't be rendering
int fromX = 0;
if(boundsClip.getMinX() > boundsShape.getMinX()) {
fromX = (int) Math.floor((boundsClip.getMinX() - boundsShape.getMinX()) / stippleSize.getWidth());
}
if(boundsClip.getMaxX() < boundsShape.getMaxX()) {
toX -= (int) Math.floor((boundsShape.getMaxX() - boundsClip.getMaxX()) / stippleSize.getWidth());
}
// adjust the iteration indexes to avoid iterating a lot over areas that we won't be rendering
int fromY = 0;
if(boundsClip.getMinY() > boundsShape.getMinY()) {
fromY = (int) Math.floor((boundsClip.getMinY() - boundsShape.getMinY()) / stippleSize.getHeight());
}
if(boundsClip.getMaxY() < boundsShape.getMaxY()) {
toY -= (int) Math.floor((boundsShape.getMaxY() - boundsClip.getMaxY()) / stippleSize.getHeight());
}
// builds the JTS geometry for the translated stipple
GeometryFactory geomFactory = new GeometryFactory();
Coordinate stippleCoord = new Coordinate(stippleSize.getCenterX(), stippleSize.getCenterY());
Geometry stipplePoint = geomFactory.createPoint(stippleCoord);
// builds a LiteShape2 object from the JTS geometry
AffineTransform2D identityTransf = new AffineTransform2D(new AffineTransform());
Decimator nullDecimator = new Decimator(-1, -1);
// paints graphic fill as a stipple
for (int i = fromX; i <= toX; i++)
{
for (int j = fromY; j <= toY; j++)
{
// computes this stipple's shift in the X and Y directions
double translateX = boundsShape.getMinX() + i * stippleSize.getWidth();
double translateY = boundsShape.getMinY() + j * stippleSize.getHeight();
// translate the stipple point
stippleCoord.x = stippleSize.getCenterX() + translateX;
stippleCoord.y = stippleSize.getCenterY() + translateY;
stipplePoint.geometryChanged();
LiteShape2 stippleShape;
try {
stippleShape = new LiteShape2(stipplePoint, identityTransf, nullDecimator, false);
} catch(Exception e) {
throw new RuntimeException("Unxpected exception building lite shape", e);
}
paint(g, stippleShape, graphicFill, scale);
}
}
}
public static class TextureAnchorKey extends Key {
protected TextureAnchorKey() {
super(0);
}
@Override
public boolean isCompatibleValue(Object val) {
return val instanceof Point2D;
}
}
}