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.geometry.jts;
017:
018: import java.awt.Rectangle;
019: import java.awt.Shape;
020: import java.awt.geom.AffineTransform;
021: import java.awt.geom.PathIterator;
022: import java.awt.geom.Point2D;
023: import java.awt.geom.Rectangle2D;
024:
025: import com.vividsolutions.jts.geom.Coordinate;
026: import com.vividsolutions.jts.geom.Envelope;
027: import com.vividsolutions.jts.geom.Geometry;
028: import com.vividsolutions.jts.geom.GeometryCollection;
029: import com.vividsolutions.jts.geom.GeometryFactory;
030: import com.vividsolutions.jts.geom.LineString;
031: import com.vividsolutions.jts.geom.LinearRing;
032: import com.vividsolutions.jts.geom.Point;
033: import com.vividsolutions.jts.geom.Polygon;
034: import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
035:
036: /**
037: * A thin wrapper that adapts a JTS geometry to the Shape interface so that the
038: * geometry can be used by java2d without coordinate cloning
039: *
040: * @author Andrea Aime
041: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/geometry/jts/LiteShape.java $
042: * @version $Id: LiteShape.java 25075 2007-04-09 19:20:46Z desruisseaux $
043: */
044: public class LiteShape implements Shape, Cloneable {
045: /** The wrapped JTS geometry */
046: private Geometry geometry;
047:
048: /** The transform needed to go from the object space to the device space */
049: private AffineTransform affineTransform = null;
050: private boolean generalize = false;
051: private double maxDistance = 1;
052:
053: // cached iterators
054: private LineIterator lineIterator = new LineIterator();
055: private GeomCollectionIterator collIterator = new GeomCollectionIterator();
056:
057: private float xScale;
058:
059: private float yScale;
060:
061: private GeometryFactory geomFac;
062:
063: /**
064: * Creates a new LiteShape object.
065: *
066: * @param geom - the wrapped geometry
067: * @param at - the transformation applied to the geometry in order to get to the shape points
068: * @param generalize - set to true if the geometry need to be generalized
069: * during rendering
070: * @param maxDistance - distance used in the generalization process
071: */
072: public LiteShape(Geometry geom, AffineTransform at,
073: boolean generalize, double maxDistance) {
074: this (geom, at, generalize);
075: this .maxDistance = maxDistance;
076: }
077:
078: /**
079: * Creates a new LiteShape object.
080: *
081: * @param geom - the wrapped geometry
082: * @param at - the transformation applied to the geometry in order to get to the shape points
083: * @param generalize - set to true if the geometry need to be generalized
084: * during rendering
085: *
086: */
087: public LiteShape(Geometry geom, AffineTransform at,
088: boolean generalize) {
089: if (geom != null)
090: this .geometry = getGeometryFactory().createGeometry(geom);
091: this .affineTransform = at;
092: this .generalize = generalize;
093: if (at == null) {
094: yScale = xScale = 1;
095: return;
096: }
097: xScale = (float) Math.sqrt((at.getScaleX() * at.getScaleX())
098: + (at.getShearX() * at.getShearX()));
099: yScale = (float) Math.sqrt((at.getScaleY() * at.getScaleY())
100: + (at.getShearY() * at.getShearY()));
101: }
102:
103: private GeometryFactory getGeometryFactory() {
104: if (geomFac == null) {
105: geomFac = new GeometryFactory(
106: new PackedCoordinateSequenceFactory());
107: }
108:
109: return geomFac;
110: }
111:
112: /**
113: * Sets the geometry contained in this lite shape. Convenient to reuse this
114: * object instead of creating it again and again during rendering
115: *
116: * @param g
117: */
118: public void setGeometry(Geometry g) {
119: this .geometry = (Geometry) g.clone();
120: }
121:
122: /**
123: * Tests if the interior of the <code>Shape</code> entirely contains the
124: * specified <code>Rectangle2D</code>. This method might conservatively
125: * return <code>false</code> when:
126: *
127: * <ul>
128: * <li>
129: * the <code>intersect</code> method returns <code>true</code> and
130: * </li>
131: * <li>
132: * the calculations to determine whether or not the <code>Shape</code>
133: * entirely contains the <code>Rectangle2D</code> are prohibitively
134: * expensive.
135: * </li>
136: * </ul>
137: *
138: * This means that this method might return <code>false</code> even though
139: * the <code>Shape</code> contains the <code>Rectangle2D</code>. The
140: * <code>Area</code> class can be used to perform more accurate
141: * computations of geometric intersection for any <code>Shape</code>
142: * object if a more precise answer is required.
143: *
144: * @param r The specified <code>Rectangle2D</code>
145: *
146: * @return <code>true</code> if the interior of the <code>Shape</code>
147: * entirely contains the <code>Rectangle2D</code>;
148: * <code>false</code> otherwise or, if the <code>Shape</code>
149: * contains the <code>Rectangle2D</code> and the
150: * <code>intersects</code> method returns <code>true</code> and
151: * the containment calculations would be too expensive to perform.
152: *
153: * @see #contains(double, double, double, double)
154: */
155: public boolean contains(Rectangle2D r) {
156: Geometry rect = rectangleToGeometry(r);
157:
158: return geometry.contains(rect);
159: }
160:
161: /**
162: * Tests if a specified {@link Point2D} is inside the boundary of the
163: * <code>Shape</code>.
164: *
165: * @param p a specified <code>Point2D</code>
166: *
167: * @return <code>true</code> if the specified <code>Point2D</code> is
168: * inside the boundary of the <code>Shape</code>;
169: * <code>false</code> otherwise.
170: */
171: public boolean contains(Point2D p) {
172: Coordinate coord = new Coordinate(p.getX(), p.getY());
173: Geometry point = geometry.getFactory().createPoint(coord);
174:
175: return geometry.contains(point);
176: }
177:
178: /**
179: * Tests if the specified coordinates are inside the boundary of the
180: * <code>Shape</code>.
181: *
182: * @param x the specified coordinates, x value
183: * @param y the specified coordinates, y value
184: *
185: * @return <code>true</code> if the specified coordinates are inside the
186: * <code>Shape</code> boundary; <code>false</code> otherwise.
187: */
188: public boolean contains(double x, double y) {
189: Coordinate coord = new Coordinate(x, y);
190: Geometry point = geometry.getFactory().createPoint(coord);
191:
192: return geometry.contains(point);
193: }
194:
195: /**
196: * Tests if the interior of the <code>Shape</code> entirely contains the
197: * specified rectangular area. All coordinates that lie inside the
198: * rectangular area must lie within the <code>Shape</code> for the entire
199: * rectanglar area to be considered contained within the
200: * <code>Shape</code>.
201: *
202: * <p>
203: * This method might conservatively return <code>false</code> when:
204: *
205: * <ul>
206: * <li>
207: * the <code>intersect</code> method returns <code>true</code> and
208: * </li>
209: * <li>
210: * the calculations to determine whether or not the <code>Shape</code>
211: * entirely contains the rectangular area are prohibitively expensive.
212: * </li>
213: * </ul>
214: *
215: * This means that this method might return <code>false</code> even though
216: * the <code>Shape</code> contains the rectangular area. The
217: * <code>Area</code> class can be used to perform more accurate
218: * computations of geometric intersection for any <code>Shape</code>
219: * object if a more precise answer is required.
220: * </p>
221: *
222: * @param x the coordinates of the specified rectangular area, x value
223: * @param y the coordinates of the specified rectangular area, y value
224: * @param w the width of the specified rectangular area
225: * @param h the height of the specified rectangular area
226: *
227: * @return <code>true</code> if the interior of the <code>Shape</code>
228: * entirely contains the specified rectangular area;
229: * <code>false</code> otherwise or, if the <code>Shape</code>
230: * contains the rectangular area and the <code>intersects</code>
231: * method returns <code>true</code> and the containment
232: * calculations would be too expensive to perform.
233: *
234: * @see java.awt.geom.Area
235: * @see #intersects
236: */
237: public boolean contains(double x, double y, double w, double h) {
238: Geometry rect = createRectangle(x, y, w, h);
239:
240: return geometry.contains(rect);
241: }
242:
243: /**
244: * Returns an integer {@link Rectangle} that completely encloses the
245: * <code>Shape</code>. Note that there is no guarantee that the returned
246: * <code>Rectangle</code> is the smallest bounding box that encloses the
247: * <code>Shape</code>, only that the <code>Shape</code> lies entirely
248: * within the indicated <code>Rectangle</code>. The returned
249: * <code>Rectangle</code> might also fail to completely enclose the
250: * <code>Shape</code> if the <code>Shape</code> overflows the limited
251: * range of the integer data type. The <code>getBounds2D</code> method
252: * generally returns a tighter bounding box due to its greater flexibility
253: * in representation.
254: *
255: * @return an integer <code>Rectangle</code> that completely encloses the
256: * <code>Shape</code>.
257: *
258: * @see #getBounds2D
259: */
260: public Rectangle getBounds() {
261: Coordinate[] coords = geometry.getEnvelope().getCoordinates();
262:
263: // get out corners. the documentation doens't specify in which
264: // order the bounding box coordinates are returned
265: double x1;
266:
267: // get out corners. the documentation doens't specify in which
268: // order the bounding box coordinates are returned
269: double y1;
270:
271: // get out corners. the documentation doens't specify in which
272: // order the bounding box coordinates are returned
273: double x2;
274:
275: // get out corners. the documentation doens't specify in which
276: // order the bounding box coordinates are returned
277: double y2;
278: x1 = x2 = coords[0].x;
279: y1 = y2 = coords[0].y;
280:
281: for (int i = 1; i < 3; i++) {
282: double x = coords[i].x;
283: double y = coords[i].y;
284:
285: if (x < x1) {
286: x1 = x;
287: }
288:
289: if (x > x2) {
290: x2 = x;
291: }
292:
293: if (y < y1) {
294: y1 = y;
295: }
296:
297: if (y > y2) {
298: y2 = y;
299: }
300: }
301:
302: x1 = Math.ceil(x1);
303: x2 = Math.floor(x2);
304: y1 = Math.ceil(y1);
305: y2 = Math.floor(y2);
306:
307: return new Rectangle((int) x1, (int) y1, (int) (x2 - x1),
308: (int) (y2 - y1));
309: }
310:
311: /**
312: * Returns a high precision and more accurate bounding box of the
313: * <code>Shape</code> than the <code>getBounds</code> method. Note that
314: * there is no guarantee that the returned {@link Rectangle2D} is the
315: * smallest bounding box that encloses the <code>Shape</code>, only that
316: * the <code>Shape</code> lies entirely within the indicated
317: * <code>Rectangle2D</code>. The bounding box returned by this method is
318: * usually tighter than that returned by the <code>getBounds</code> method
319: * and never fails due to overflow problems since the return value can be
320: * an instance of the <code>Rectangle2D</code> that uses double precision
321: * values to store the dimensions.
322: *
323: * @return an instance of <code>Rectangle2D</code> that is a high-precision
324: * bounding box of the <code>Shape</code>.
325: *
326: * @see #getBounds
327: */
328: public Rectangle2D getBounds2D() {
329: Envelope env = geometry.getEnvelopeInternal();
330: return new Rectangle2D.Double(env.getMinX(), env.getMinY(), env
331: .getWidth(), env.getHeight());
332: }
333:
334: /**
335: * Returns an iterator object that iterates along the <code>Shape</code>
336: * boundary and provides access to the geometry of the <code>Shape</code>
337: * outline. If an optional {@link AffineTransform} is specified, the
338: * coordinates returned in the iteration are transformed accordingly.
339: *
340: * <p>
341: * Each call to this method returns a fresh <code>PathIterator</code>
342: * object that traverses the geometry of the <code>Shape</code> object
343: * independently from any other <code>PathIterator</code> objects in use
344: * at the same time.
345: * </p>
346: *
347: * <p>
348: * It is recommended, but not guaranteed, that objects implementing the
349: * <code>Shape</code> interface isolate iterations that are in process
350: * from any changes that might occur to the original object's geometry
351: * during such iterations.
352: * </p>
353: *
354: * <p>
355: * Before using a particular implementation of the <code>Shape</code>
356: * interface in more than one thread simultaneously, refer to its
357: * documentation to verify that it guarantees that iterations are isolated
358: * from modifications.
359: * </p>
360: *
361: * @param at an optional <code>AffineTransform</code> to be applied to the
362: * coordinates as they are returned in the iteration, or
363: * <code>null</code> if untransformed coordinates are desired
364: *
365: * @return a new <code>PathIterator</code> object, which independently
366: * traverses the geometry of the <code>Shape</code>.
367: */
368: public PathIterator getPathIterator(AffineTransform at) {
369: AbstractLiteIterator pi = null;
370:
371: AffineTransform combined = null;
372:
373: if (affineTransform == null) {
374: combined = at;
375: } else if ((at == null) || at.isIdentity()) {
376: combined = affineTransform;
377: } else {
378: combined = new AffineTransform(affineTransform);
379: combined.concatenate(at);
380: }
381:
382: // return iterator according to the kind of geometry we include
383: if (this .geometry instanceof Point) {
384: pi = new PointIterator((Point) geometry, combined);
385: }
386:
387: if (this .geometry instanceof Polygon) {
388:
389: pi = new PolygonIterator((Polygon) geometry, combined,
390: generalize, maxDistance);
391: } else if (this .geometry instanceof LinearRing) {
392: lineIterator.init((LinearRing) geometry, combined,
393: generalize, (float) maxDistance);
394: pi = lineIterator;
395: } else if (this .geometry instanceof LineString) {
396: // if(((LineString) geometry).getCoordinateSequence() instanceof PackedCoordinateSequence.Double)
397: // pi = new PackedLineIterator((LineString) geometry, combined, generalize,
398: // (float) maxDistance);
399: // else
400: if (combined == affineTransform)
401: lineIterator
402: .init((LineString) geometry, combined,
403: generalize, (float) maxDistance,
404: xScale, yScale);
405: else
406: lineIterator.init((LineString) geometry, combined,
407: generalize, (float) maxDistance);
408: pi = lineIterator;
409: } else if (this .geometry instanceof GeometryCollection) {
410: collIterator.init((GeometryCollection) geometry, combined,
411: generalize, maxDistance);
412: pi = collIterator;
413: }
414:
415: return pi;
416: }
417:
418: /**
419: * Returns an iterator object that iterates along the <code>Shape</code>
420: * boundary and provides access to a flattened view of the
421: * <code>Shape</code> outline geometry.
422: *
423: * <p>
424: * Only SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point types are returned by
425: * the iterator.
426: * </p>
427: *
428: * <p>
429: * If an optional <code>AffineTransform</code> is specified, the
430: * coordinates returned in the iteration are transformed accordingly.
431: * </p>
432: *
433: * <p>
434: * The amount of subdivision of the curved segments is controlled by the
435: * <code>flatness</code> parameter, which specifies the maximum distance
436: * that any point on the unflattened transformed curve can deviate from
437: * the returned flattened path segments. Note that a limit on the accuracy
438: * of the flattened path might be silently imposed, causing very small
439: * flattening parameters to be treated as larger values. This limit, if
440: * there is one, is defined by the particular implementation that is used.
441: * </p>
442: *
443: * <p>
444: * Each call to this method returns a fresh <code>PathIterator</code>
445: * object that traverses the <code>Shape</code> object geometry
446: * independently from any other <code>PathIterator</code> objects in use
447: * at the same time.
448: * </p>
449: *
450: * <p>
451: * It is recommended, but not guaranteed, that objects implementing the
452: * <code>Shape</code> interface isolate iterations that are in process
453: * from any changes that might occur to the original object's geometry
454: * during such iterations.
455: * </p>
456: *
457: * <p>
458: * Before using a particular implementation of this interface in more than
459: * one thread simultaneously, refer to its documentation to verify that it
460: * guarantees that iterations are isolated from modifications.
461: * </p>
462: *
463: * @param at an optional <code>AffineTransform</code> to be applied to the
464: * coordinates as they are returned in the iteration, or
465: * <code>null</code> if untransformed coordinates are desired
466: * @param flatness the maximum distance that the line segments used to
467: * approximate the curved segments are allowed to deviate from any
468: * point on the original curve
469: *
470: * @return a new <code>PathIterator</code> that independently traverses the
471: * <code>Shape</code> geometry.
472: */
473: public PathIterator getPathIterator(AffineTransform at,
474: double flatness) {
475: return getPathIterator(at);
476: }
477:
478: /**
479: * Tests if the interior of the <code>Shape</code> intersects the interior
480: * of a specified <code>Rectangle2D</code>. This method might
481: * conservatively return <code>true</code> when:
482: *
483: * <ul>
484: * <li>
485: * there is a high probability that the <code>Rectangle2D</code> and the
486: * <code>Shape</code> intersect, but
487: * </li>
488: * <li>
489: * the calculations to accurately determine this intersection are
490: * prohibitively expensive.
491: * </li>
492: * </ul>
493: *
494: * This means that this method might return <code>true</code> even though
495: * the <code>Rectangle2D</code> does not intersect the <code>Shape</code>.
496: *
497: * @param r the specified <code>Rectangle2D</code>
498: *
499: * @return <code>true</code> if the interior of the <code>Shape</code> and
500: * the interior of the specified <code>Rectangle2D</code>
501: * intersect, or are both highly likely to intersect and
502: * intersection calculations would be too expensive to
503: * perform; <code>false</code> otherwise.
504: *
505: * @see #intersects(double, double, double, double)
506: */
507: public boolean intersects(Rectangle2D r) {
508: Geometry rect = rectangleToGeometry(r);
509:
510: return geometry.intersects(rect);
511: }
512:
513: /**
514: * Tests if the interior of the <code>Shape</code> intersects the interior
515: * of a specified rectangular area. The rectangular area is considered to
516: * intersect the <code>Shape</code> if any point is contained in both the
517: * interior of the <code>Shape</code> and the specified rectangular area.
518: *
519: * <p>
520: * This method might conservatively return <code>true</code> when:
521: *
522: * <ul>
523: * <li>
524: * there is a high probability that the rectangular area and the
525: * <code>Shape</code> intersect, but
526: * </li>
527: * <li>
528: * the calculations to accurately determine this intersection are
529: * prohibitively expensive.
530: * </li>
531: * </ul>
532: *
533: * This means that this method might return <code>true</code> even though
534: * the rectangular area does not intersect the <code>Shape</code>. The
535: * {@link java.awt.geom.Area Area} class can be used to perform more
536: * accurate computations of geometric intersection for any
537: * <code>Shape</code> object if a more precise answer is required.
538: * </p>
539: *
540: * @param x the coordinates of the specified rectangular area, x value
541: * @param y the coordinates of the specified rectangular area, y value
542: * @param w the width of the specified rectangular area
543: * @param h the height of the specified rectangular area
544: *
545: * @return <code>true</code> if the interior of the <code>Shape</code> and
546: * the interior of the rectangular area intersect, or are both
547: * highly likely to intersect and intersection calculations would
548: * be too expensive to perform; <code>false</code> otherwise.
549: *
550: * @see java.awt.geom.Area
551: */
552: public boolean intersects(double x, double y, double w, double h) {
553: Geometry rect = createRectangle(x, y, w, h);
554:
555: return geometry.intersects(rect);
556: }
557:
558: /**
559: * Converts the Rectangle2D passed as parameter in a jts Geometry object
560: *
561: * @param r the rectangle to be converted
562: *
563: * @return a geometry with the same vertices as the rectangle
564: */
565: private Geometry rectangleToGeometry(Rectangle2D r) {
566: return createRectangle(r.getMinX(), r.getMinY(), r.getWidth(),
567: r.getHeight());
568: }
569:
570: /**
571: * Creates a jts Geometry object representing a rectangle with the given
572: * parameters
573: *
574: * @param x left coordinate
575: * @param y bottom coordinate
576: * @param w width
577: * @param h height
578: *
579: * @return a rectangle with the specified position and size
580: */
581: private Geometry createRectangle(double x, double y, double w,
582: double h) {
583: Coordinate[] coords = { new Coordinate(x, y),
584: new Coordinate(x, y + h), new Coordinate(x + w, y + h),
585: new Coordinate(x + w, y), new Coordinate(x, y) };
586: LinearRing lr = geometry.getFactory().createLinearRing(coords);
587:
588: return geometry.getFactory().createPolygon(lr, null);
589: }
590:
591: /**
592: * Returns the affine transform for this lite shape
593: */
594: public AffineTransform getAffineTransform() {
595: return affineTransform;
596: }
597:
598: public Geometry getGeometry() {
599: return geometry;
600: }
601: }
|