001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.renderer.lite;
017:
018: import java.awt.AlphaComposite;
019: import java.awt.BasicStroke;
020: import java.awt.Canvas;
021: import java.awt.Graphics2D;
022: import java.awt.Image;
023: import java.awt.Paint;
024: import java.awt.RenderingHints;
025: import java.awt.Shape;
026: import java.awt.Stroke;
027: import java.awt.TexturePaint;
028: import java.awt.geom.AffineTransform;
029: import java.awt.geom.PathIterator;
030: import java.awt.geom.Point2D;
031: import java.awt.geom.Rectangle2D;
032: import java.awt.image.BufferedImage;
033: import java.util.logging.Level;
034: import java.util.logging.Logger;
035:
036: import org.geotools.geometry.jts.GeomCollectionIterator;
037: import org.geotools.geometry.jts.LiteShape2;
038: import org.geotools.renderer.style.GraphicStyle2D;
039: import org.geotools.renderer.style.LineStyle2D;
040: import org.geotools.renderer.style.MarkStyle2D;
041: import org.geotools.renderer.style.PolygonStyle2D;
042: import org.geotools.renderer.style.Style2D;
043:
044: import com.vividsolutions.jts.geom.Geometry;
045: import com.vividsolutions.jts.geom.GeometryCollection;
046:
047: /**
048: * A simple class that knows how to paint a Shape object onto a Graphic given a
049: * Style2D. It's the last step of the rendering engine, and has been factored
050: * out since both renderers do use the same painting logic.
051: *
052: * @author Andrea Aime
053: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/render/src/main/java/org/geotools/renderer/lite/StyledShapePainter.java $
054: */
055: public final class StyledShapePainter {
056: private final static AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
057:
058: /** Observer for image loading */
059: private final static Canvas imgObserver = new Canvas();
060:
061: /** The logger for the rendering module. */
062: private final static Logger LOGGER = org.geotools.util.logging.Logging
063: .getLogger(StyledShapePainter.class.getName());
064:
065: LabelCache labelCache;
066:
067: /**
068: * Construct <code>StyledShapePainter</code>.
069: */
070: public StyledShapePainter(LabelCache labelCache) {
071: this .labelCache = labelCache;
072: }
073:
074: /**
075: * Invoked automatically when a polyline is about to be draw. This
076: * implementation paints the polyline according to the rendered style
077: *
078: * @param graphics
079: * The graphics in which to draw.
080: * @param shape
081: * The polygon to draw.
082: * @param style
083: * The style to apply, or <code>null</code> if none.
084: * @param scale
085: * The scale denominator for the current zoom level
086: */
087: public void paint(final Graphics2D graphics,
088: final LiteShape2 shape, final Style2D style,
089: final double scale) {
090: if (style == null) {
091: // TODO: what's going on? Should not be reached...
092: LOGGER
093: .severe("ShapePainter has been asked to paint a null style!!");
094:
095: return;
096: }
097:
098: // Is the current scale within the style scale range?
099: if (!style.isScaleInRange(scale)) {
100: LOGGER.fine("Out of scale");
101: return;
102: }
103:
104: if (style instanceof MarkStyle2D) {
105: // DJB: changed this to handle multi* geometries and line and
106: // polygon geometries better
107: GeometryCollection gc;
108: if (shape.getGeometry() instanceof GeometryCollection)
109: gc = (GeometryCollection) shape.getGeometry();
110: else {
111: Geometry[] gs = new Geometry[1];
112: gs[0] = shape.getGeometry();
113: gc = shape.getGeometry().getFactory()
114: .createGeometryCollection(gs); // make a Point,Line, or Poly into a GC
115: }
116: GeomCollectionIterator citer = new GeomCollectionIterator(
117: gc, IDENTITY_TRANSFORM, false, 1.0);
118:
119: // get the point onto the shape has to be painted
120: float[] coords = new float[2];
121: MarkStyle2D ms2d = (MarkStyle2D) style;
122:
123: Shape transformedShape;
124: while (!(citer.isDone())) {
125: citer.currentSegment(coords);
126: transformedShape = ms2d.getTransformedShape(coords[0],
127: coords[1]);
128: if (transformedShape != null) {
129: if (ms2d.getFill() != null) {
130: graphics.setPaint(ms2d.getFill());
131: graphics.setComposite(ms2d.getFillComposite());
132: graphics.fill(transformedShape);
133: }
134:
135: if (ms2d.getContour() != null) {
136: graphics.setPaint(ms2d.getContour());
137: graphics.setStroke(ms2d.getStroke());
138: graphics.setComposite(ms2d
139: .getContourComposite());
140: graphics.draw(transformedShape);
141: }
142: citer.next();
143: }
144: }
145: } else if (style instanceof GraphicStyle2D) {
146: // DJB: TODO: almost certainly you want to do the same here as with
147: // the MarkStyle2D (above)
148: // get the point onto the shape has to be painted
149: float[] coords = new float[2];
150: PathIterator iter = shape
151: .getPathIterator(IDENTITY_TRANSFORM);
152: iter.currentSegment(coords);
153:
154: GraphicStyle2D gs2d = (GraphicStyle2D) style;
155:
156: renderImage(graphics, coords[0], coords[1], (Image) gs2d
157: .getImage(), gs2d.getRotation(), gs2d.getOpacity());
158: } else {
159: // if the style is a polygon one, process it even if the polyline is
160: // not closed (by SLD specification)
161: if (style instanceof PolygonStyle2D) {
162: PolygonStyle2D ps2d = (PolygonStyle2D) style;
163:
164: if (ps2d.getFill() != null) {
165: Paint paint = ps2d.getFill();
166:
167: if (paint instanceof TexturePaint) {
168: TexturePaint tp = (TexturePaint) paint;
169: BufferedImage image = tp.getImage();
170: Rectangle2D rect = tp.getAnchorRect();
171: AffineTransform at = graphics.getTransform();
172: double width = rect.getWidth() * at.getScaleX();
173: double height = -1.0 * rect.getHeight()
174: * at.getScaleY();// DJB: -1 because its
175: // flipped upside down by
176: // default. This flips it
177: // up.
178: Rectangle2D scaledRect = new Rectangle2D.Double(
179: 0, 0, width, height);
180: paint = new TexturePaint(image, scaledRect);
181: }
182:
183: graphics.setPaint(paint);
184: graphics.setComposite(ps2d.getFillComposite());
185: graphics.fill(shape);
186: }
187: }
188:
189: if (style instanceof LineStyle2D) {
190: LineStyle2D ls2d = (LineStyle2D) style;
191:
192: if (ls2d.getStroke() != null) {
193: // see if a graphic stroke is to be used, the drawing method
194: // is completely
195: // different in this case
196: if (ls2d.getGraphicStroke() != null) {
197: drawWithGraphicsStroke(graphics, shape, ls2d
198: .getGraphicStroke());
199: } else {
200: Paint paint = ls2d.getContour();
201:
202: if (paint instanceof TexturePaint) {
203: TexturePaint tp = (TexturePaint) paint;
204: BufferedImage image = tp.getImage();
205: Rectangle2D rect = tp.getAnchorRect();
206: AffineTransform at = graphics
207: .getTransform();
208: double width = rect.getWidth()
209: * at.getScaleX();
210: double height = rect.getHeight()
211: * at.getScaleY();
212: Rectangle2D scaledRect = new Rectangle2D.Double(
213: 0, 0, width, height);
214: paint = new TexturePaint(image, scaledRect);
215: }
216:
217: // debugShape(shape);
218: Stroke stroke = ls2d.getStroke();
219: if (graphics
220: .getRenderingHint(RenderingHints.KEY_ANTIALIASING) == RenderingHints.VALUE_ANTIALIAS_ON) {
221: if (stroke instanceof BasicStroke) {
222: BasicStroke bs = (BasicStroke) stroke;
223: stroke = new BasicStroke(bs
224: .getLineWidth() + 0.5f, bs
225: .getEndCap(), bs.getLineJoin(),
226: bs.getMiterLimit(), bs
227: .getDashArray(), bs
228: .getDashPhase());
229: }
230: }
231:
232: graphics.setPaint(paint);
233: graphics.setStroke(stroke);
234: graphics.setComposite(ls2d
235: .getContourComposite());
236: graphics.draw(shape);
237: }
238: }
239: }
240: }
241: }
242:
243: public void debugShape(Shape shape) {
244: float[] pt = new float[2];
245: PathIterator iter = shape.getPathIterator(null);
246: while (!(iter.isDone())) {
247:
248: int type = iter.currentSegment(pt);
249: String event = "unknown";
250: if (type == PathIterator.SEG_CLOSE)
251: event = "SEG_CLOSE";
252: if (type == PathIterator.SEG_CUBICTO)
253: event = "SEG_CUBIC";
254: if (type == PathIterator.SEG_LINETO)
255: event = "SEG_LINETO";
256: if (type == PathIterator.SEG_MOVETO)
257: event = "SEG_MOVETO";
258: if (type == PathIterator.SEG_QUADTO)
259: event = "SEG_QUADTO";
260: System.out.println(event + " " + pt[0] + "," + pt[1]);
261: iter.next();
262: }
263: }
264:
265: // draws the image along the path
266: private void drawWithGraphicsStroke(Graphics2D graphics,
267: Shape shape, BufferedImage image) {
268: PathIterator pi = shape.getPathIterator(null, 10.0);
269: double[] coords = new double[4];
270: int type;
271:
272: // I suppose the image has been already scaled and its square
273: int imageSize = image.getWidth();
274:
275: double[] first = new double[2];
276: double[] previous = new double[2];
277: type = pi.currentSegment(coords);
278: first[0] = coords[0];
279: first[1] = coords[1];
280: previous[0] = coords[0];
281: previous[1] = coords[1];
282:
283: if (LOGGER.isLoggable(Level.FINEST)) {
284: LOGGER.finest("starting at " + first[0] + "," + first[1]);
285: }
286:
287: pi.next();
288:
289: while (!pi.isDone()) {
290: type = pi.currentSegment(coords);
291:
292: switch (type) {
293: case PathIterator.SEG_MOVETO:
294:
295: // nothing to do?
296: if (LOGGER.isLoggable(Level.FINEST)) {
297: LOGGER.finest("moving to " + coords[0] + ","
298: + coords[1]);
299: }
300:
301: break;
302:
303: case PathIterator.SEG_CLOSE:
304:
305: // draw back to first from previous
306: coords[0] = first[0];
307: coords[1] = first[1];
308:
309: if (LOGGER.isLoggable(Level.FINEST)) {
310: LOGGER.finest("closing from " + previous[0] + ","
311: + previous[1] + " to " + coords[0] + ","
312: + coords[1]);
313: }
314:
315: // no break here - fall through to next section
316: case PathIterator.SEG_LINETO:
317:
318: // draw from previous to coords
319: if (LOGGER.isLoggable(Level.FINEST)) {
320: LOGGER.finest("drawing from " + previous[0] + ","
321: + previous[1] + " to " + coords[0] + ","
322: + coords[1]);
323: }
324:
325: double dx = coords[0] - previous[0];
326: double dy = coords[1] - previous[1];
327: double len = Math.sqrt((dx * dx) + (dy * dy)); // - imageWidth;
328:
329: double theta = Math.atan2(dx, dy);
330: dx = (Math.sin(theta) * imageSize);
331: dy = (Math.cos(theta) * imageSize);
332:
333: if (LOGGER.isLoggable(Level.FINEST)) {
334: LOGGER.finest("dx = " + dx + " dy " + dy
335: + " step = "
336: + Math.sqrt((dx * dx) + (dy * dy)));
337: }
338:
339: double rotation = -(theta - (Math.PI / 2d));
340: double x = previous[0] + (dx / 2.0);
341: double y = previous[1] + (dy / 2.0);
342:
343: if (LOGGER.isLoggable(Level.FINEST)) {
344: LOGGER.finest("len =" + len + " imageSize "
345: + imageSize);
346: }
347:
348: double dist = 0;
349:
350: for (dist = 0; dist < (len - imageSize); dist += imageSize) {
351: /* graphic.drawImage(image2,(int)x-midx,(int)y-midy,null); */
352: renderImage(graphics, x, y, image, rotation, 1);
353:
354: x += dx;
355: y += dy;
356: }
357:
358: if (LOGGER.isLoggable(Level.FINEST)) {
359: LOGGER.finest("loop end dist " + dist + " len "
360: + len + " " + (len - dist));
361: }
362:
363: double remainder = len - dist;
364: int remainingWidth = (int) remainder;
365:
366: if (remainingWidth > 0) {
367: // clip and render image
368: if (LOGGER.isLoggable(Level.FINEST)) {
369: LOGGER.finest("about to use clipped image "
370: + remainder);
371: }
372:
373: BufferedImage img = new BufferedImage(
374: remainingWidth, imageSize, image.getType());
375: Graphics2D ig = img.createGraphics();
376: ig.drawImage(image, 0, 0, imgObserver);
377:
378: renderImage(graphics, x, y, img, rotation, 1);
379: }
380:
381: break;
382:
383: default:
384: LOGGER
385: .warning("default branch reached in drawWithGraphicStroke");
386: }
387:
388: previous[0] = coords[0];
389: previous[1] = coords[1];
390: pi.next();
391: }
392: }
393:
394: /**
395: * Renders an image on the device
396: *
397: * @param graphics
398: * the image location on the screen, x coordinate
399: * @param x
400: * the image location on the screen, y coordinate
401: * @param y
402: * the image
403: * @param image
404: * DOCUMENT ME!
405: * @param rotation
406: * the image rotatation
407: * @param opacity
408: * DOCUMENT ME!
409: */
410: private void renderImage(Graphics2D graphics, double x, double y,
411: Image image, double rotation, float opacity) {
412: if (LOGGER.isLoggable(Level.FINEST)) {
413: LOGGER.finest("drawing Image @" + x + "," + y);
414: }
415:
416: AffineTransform temp = graphics.getTransform();
417: AffineTransform markAT = new AffineTransform();
418: Point2D mapCentre = new java.awt.geom.Point2D.Double(x, y);
419: Point2D graphicCentre = new java.awt.geom.Point2D.Double();
420: temp.transform(mapCentre, graphicCentre);
421: markAT.translate(graphicCentre.getX(), graphicCentre.getY());
422:
423: double shearY = temp.getShearY();
424: double scaleY = temp.getScaleY();
425:
426: double originalRotation = Math.atan(shearY / scaleY);
427:
428: if (LOGGER.isLoggable(Level.FINER)) {
429: LOGGER.finer("originalRotation " + originalRotation);
430: }
431:
432: markAT.rotate(rotation);
433: graphics.setTransform(markAT);
434: graphics.setComposite(AlphaComposite.getInstance(
435: AlphaComposite.SRC_OVER, opacity));
436:
437: // we moved the origin to the centre of the image.
438: graphics.drawImage(image, -image.getWidth(imgObserver) / 2,
439: -image.getHeight(imgObserver) / 2, imgObserver);
440:
441: graphics.setTransform(temp);
442:
443: return;
444: }
445:
446: }
|