001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.resources.geometry;
018:
019: // J2SE dependencies
020: import java.awt.Shape;
021: import java.awt.geom.Rectangle2D;
022: import java.text.FieldPosition;
023: import java.text.NumberFormat;
024: import java.io.Serializable;
025:
026: // Geotools dependencies
027: import org.geotools.resources.Utilities;
028:
029: /**
030: * Serializable, high-performance double-precision rectangle. Instead of using
031: * {@code x}, {@code y}, {@code width} and {@code height},
032: * this class store rectangle's coordinates into the following fields:
033: * {@link #xmin}, {@link #xmax}, {@link #ymin} et {@link #ymax}. Methods likes
034: * {@code contains} and {@code intersects} are faster, which make this
035: * class more appropriate for using intensively inside a loop. Furthermore, this
036: * class work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
037: * {@linkplain Double#NaN NaN} values.
038: *
039: * @since 2.0
040: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/resources/geometry/XRectangle2D.java $
041: * @version $Id: XRectangle2D.java 25779 2007-06-08 17:49:00Z desruisseaux $
042: * @author Martin Desruisseaux
043: */
044: public class XRectangle2D extends Rectangle2D implements Serializable {
045: /**
046: * A small number for testing intersection between an arbitrary shape and a rectangle.
047: */
048: private static final double EPS = 1E-6;
049:
050: /**
051: * An immutable instance of a {@link Rectangle2D} with bounds extending toward
052: * infinities. The {@link #getMinX} and {@link #getMinY} methods return always
053: * {@link java.lang.Double#NEGATIVE_INFINITY}, while the {@link #getMaxX} and
054: * {@link #getMaxY} methods return always {@link java.lang.Double#POSITIVE_INFINITY}.
055: * This rectangle can be used as argument in the {@link XRectangle2D} constructor for
056: * initializing a new {@code XRectangle2D} to infinite bounds.
057: */
058: public static final Rectangle2D INFINITY = InfiniteRectangle2D.INFINITY;
059:
060: /**
061: * Serial number for interoperability with different versions.
062: */
063: private static final long serialVersionUID = -1918221103635749436L;
064:
065: /** Minimal <var>x</var> coordinate. */
066: protected double xmin;
067: /** Minimal <var>y</var> coordinate. */
068: protected double ymin;
069: /** Maximal <var>x</var> coordinate. */
070: protected double xmax;
071: /** Maximal <var>y</var> coordinate. */
072: protected double ymax;
073:
074: /**
075: * Construct a default rectangle. Initial coordinates are {@code (0,0,0,0)}.
076: */
077: public XRectangle2D() {
078: }
079:
080: /**
081: * Construct a rectangle with the specified location and dimension.
082: * This constructor uses the same signature than {@link Rectangle2D} for consistency.
083: */
084: public XRectangle2D(final double x, final double y,
085: final double width, final double height) {
086: this .xmin = x;
087: this .ymin = y;
088: this .xmax = x + width;
089: this .ymax = y + height;
090: }
091:
092: /**
093: * Construct a rectangle with the same coordinates than the supplied rectangle.
094: *
095: * @param rect The rectangle, or {@code null} in none (in which case this constructor
096: * is equivalents to the no-argument constructor). Use {@link #INFINITY} for
097: * initializing this {@code XRectangle2D} with infinite bounds.
098: */
099: public XRectangle2D(final Rectangle2D rect) {
100: if (rect != null) {
101: setRect(rect);
102: }
103: }
104:
105: /**
106: * Create a rectangle using maximal <var>x</var> and <var>y</var> values
107: * rather than width and height. This factory avoid the problem of NaN
108: * values when extremums are infinite numbers.
109: */
110: public static XRectangle2D createFromExtremums(final double xmin,
111: final double ymin, final double xmax, final double ymax) {
112: final XRectangle2D rect = new XRectangle2D();
113: rect.xmin = xmin;
114: rect.ymin = ymin;
115: rect.xmax = xmax;
116: rect.ymax = ymax;
117: return rect;
118: }
119:
120: /**
121: * Determines whether the {@code RectangularShape} is empty.
122: * When the {@code RectangularShape} is empty, it encloses no
123: * area.
124: *
125: * @return {@code true} if the {@code RectangularShape} is empty;
126: * {@code false} otherwise.
127: */
128: public boolean isEmpty() {
129: return !(xmin < xmax && ymin < ymax);
130: }
131:
132: /**
133: * Returns the X coordinate of the upper left corner of
134: * the framing rectangle in {@code double} precision.
135: *
136: * @return the x coordinate of the upper left corner of the framing rectangle.
137: */
138: public double getX() {
139: return xmin;
140: }
141:
142: /**
143: * Returns the Y coordinate of the upper left corner of
144: * the framing rectangle in {@code double} precision.
145: *
146: * @return the y coordinate of the upper left corner of the framing rectangle.
147: */
148: public double getY() {
149: return ymin;
150: }
151:
152: /**
153: * Returns the width of the framing rectangle in
154: * {@code double} precision.
155: * @return the width of the framing rectangle.
156: */
157: public double getWidth() {
158: return xmax - xmin;
159: }
160:
161: /**
162: * Returns the height of the framing rectangle in {@code double} precision.
163: *
164: * @return the height of the framing rectangle.
165: */
166: public double getHeight() {
167: return ymax - ymin;
168: }
169:
170: /**
171: * Returns the smallest X coordinate of the rectangle.
172: */
173: public double getMinX() {
174: return xmin;
175: }
176:
177: /**
178: * Returns the smallest Y coordinate of the rectangle.
179: */
180: public double getMinY() {
181: return ymin;
182: }
183:
184: /**
185: * Returns the largest X coordinate of the rectangle.
186: */
187: public double getMaxX() {
188: return xmax;
189: }
190:
191: /**
192: * Returns the largest Y coordinate of the rectangle.
193: */
194: public double getMaxY() {
195: return ymax;
196: }
197:
198: /**
199: * Returns the X coordinate of the center of the rectangle.
200: */
201: public double getCenterX() {
202: return (xmin + xmax) * 0.5;
203: }
204:
205: /**
206: * Returns the Y coordinate of the center of the rectangle.
207: */
208: public double getCenterY() {
209: return (ymin + ymax) * 0.5;
210: }
211:
212: /**
213: * Sets the location and size of this {@code Rectangle2D}
214: * to the specified double values.
215: *
216: * @param x the <var>x</var> coordinates to which to set the
217: * location of the upper left corner of this {@code Rectangle2D}
218: * @param y the <var>y</var> coordinates to which to set the
219: * location of the upper left corner of this {@code Rectangle2D}
220: * @param width the value to use to set the width of this {@code Rectangle2D}
221: * @param height the value to use to set the height of this {@code Rectangle2D}
222: */
223: public void setRect(final double x, final double y,
224: final double width, final double height) {
225: this .xmin = x;
226: this .ymin = y;
227: this .xmax = x + width;
228: this .ymax = y + height;
229: }
230:
231: /**
232: * Sets this {@code Rectangle2D} to be the same as the
233: * specified {@code Rectangle2D}.
234: *
235: * @param r the specified {@code Rectangle2D}
236: */
237: public void setRect(final Rectangle2D r) {
238: this .xmin = r.getMinX();
239: this .ymin = r.getMinY();
240: this .xmax = r.getMaxX();
241: this .ymax = r.getMaxY();
242: }
243:
244: /**
245: * Tests if the interior of this {@code Rectangle2D}
246: * intersects the interior of a specified set of rectangular
247: * coordinates.
248: *
249: * @param x the <var>x</var> coordinates of the upper left corner
250: * of the specified set of rectangular coordinates
251: * @param y the <var>y</var> coordinates of the upper left corner
252: * of the specified set of rectangular coordinates
253: * @param width the width of the specified set of rectangular coordinates
254: * @param height the height of the specified set of rectangular coordinates
255: * @return {@code true} if this {@code Rectangle2D}
256: * intersects the interior of a specified set of rectangular
257: * coordinates; {@code false} otherwise.
258: */
259: public boolean intersects(final double x, final double y,
260: final double width, final double height) {
261: if (!(xmin < xmax && ymin < ymax && width > 0 && height > 0)) {
262: return false;
263: } else {
264: return (x < xmax && y < ymax && x + width > xmin && y
265: + height > ymin);
266: }
267: }
268:
269: /**
270: * Tests if the <strong>interior</strong> of this shape intersects the
271: * <strong>interior</strong> of a specified rectangle. This methods overrides the default
272: * {@link Rectangle2D} implementation in order to work correctly with
273: * {@linkplain Double#POSITIVE_INFINITY infinites} and {@linkplain Double#NaN NaN} values.
274: *
275: * @param rect the specified rectangle.
276: * @return {@code true} if this shape and the specified rectangle intersect each other.
277: *
278: * @see #intersectInclusive(Rectangle2D, Rectangle2D)
279: */
280: public boolean intersects(final Rectangle2D rect) {
281: if (!(xmin < xmax && ymin < ymax)) {
282: return false;
283: } else {
284: final double xmin2 = rect.getMinX();
285: final double xmax2 = rect.getMaxX();
286: if (!(xmax2 > xmin2))
287: return false;
288: final double ymin2 = rect.getMinY();
289: final double ymax2 = rect.getMaxY();
290: if (!(ymax2 > ymin2))
291: return false;
292: return (xmin2 < xmax && ymin2 < ymax && xmax2 > xmin && ymax2 > ymin);
293: }
294: }
295:
296: /**
297: * Tests if the interior and/or the edge of two rectangles intersect. This method
298: * is similar to {@link #intersects(Rectangle2D)} except for the following points:
299: * <ul>
300: * <li>This method doesn't test only if the <em>interiors</em> intersect.
301: * It tests for the edges as well.</li>
302: * <li>This method tests also rectangle with zero {@linkplain Rectangle2D#getWidth width} or
303: * {@linkplain Rectangle2D#getHeight height} (which are {@linkplain Rectangle2D#isEmpty
304: * empty} according {@link Shape} contract). However, rectangle with negative width or
305: * height are still considered as empty.</li>
306: * <li>This method work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
307: * {@linkplain Double#NaN NaN} values.</li>
308: * </ul>
309: *
310: * This method is said <cite>inclusive</cite> because it tests bounds as closed interval
311: * rather then open interval (the default Java2D behavior). Usage of closed interval is
312: * required if at least one rectangle may be the bounding box of a perfectly horizontal
313: * or vertical line; such a bounding box has 0 width or height.
314: *
315: * @param rect1 The first rectangle to test.
316: * @param rect2 The second rectangle to test.
317: * @return {@code true} if the interior and/or the edge of the two specified rectangles
318: * intersects.
319: */
320: public static boolean intersectInclusive(final Rectangle2D rect1,
321: final Rectangle2D rect2) {
322: final double xmin1 = rect1.getMinX();
323: final double xmax1 = rect1.getMaxX();
324: if (!(xmax1 >= xmin1))
325: return false;
326: final double ymin1 = rect1.getMinY();
327: final double ymax1 = rect1.getMaxY();
328: if (!(ymax1 >= ymin1))
329: return false;
330: final double xmin2 = rect2.getMinX();
331: final double xmax2 = rect2.getMaxX();
332: if (!(xmax2 >= xmin2))
333: return false;
334: final double ymin2 = rect2.getMinY();
335: final double ymax2 = rect2.getMaxY();
336: if (!(ymax2 >= ymin2))
337: return false;
338: return (xmax2 >= xmin1 && ymax2 >= ymin1 && xmin2 <= xmax1 && ymin2 <= ymax1);
339: }
340:
341: /**
342: * Tests if the interior of the {@code Shape} intersects the interior of a specified
343: * rectangle. This method might conservatively return {@code true} when there is a high
344: * probability that the rectangle and the shape intersect, but the calculations to accurately
345: * determine this intersection are prohibitively expensive. This is similar to
346: * {@link Shape#intersects(Rectangle2D)}, except that this method tests also rectangle with
347: * zero {@linkplain Rectangle2D#getWidth width} or {@linkplain Rectangle2D#getHeight height}
348: * (which are {@linkplain Rectangle2D#isEmpty empty} according {@link Shape} contract). However,
349: * rectangle with negative width or height are still considered as empty.
350: * <br><br>
351: * This method is said <cite>inclusive</cite> because it try to mimic
352: * {@link #intersectInclusive(Rectangle2D, Rectangle2D)} behavior, at
353: * least for rectangle with zero width or height.
354: *
355: * @param shape The shape.
356: * @param rect The rectangle to test for inclusion.
357: * @return {@code true} if the interior of the shape and the interior of the specified
358: * rectangle intersect, or are both highly likely to intersect.
359: */
360: public static boolean intersectInclusive(final Shape shape,
361: final Rectangle2D rect) {
362: double x = rect.getX();
363: double y = rect.getY();
364: double width = rect.getWidth();
365: double height = rect.getHeight();
366: if (width == 0 && height == 0) {
367: width = EPS;
368: height = EPS;
369: } else if (width == 0) {
370: width = height * EPS;
371: x -= 0.5 * width;
372: } else if (height == 0) {
373: height = width * EPS;
374: y -= 0.5 * height;
375: }
376: return shape.intersects(x, y, width, height);
377: }
378:
379: /**
380: * Returns {@code true} if the two rectangles are equals up to an epsilon value.
381: */
382: public static boolean equalsEpsilon(final Rectangle2D rect1,
383: final Rectangle2D rect2) {
384: double dx = 0.5 * Math.abs(rect1.getWidth() + rect2.getWidth());
385: double dy = 0.5 * Math.abs(rect1.getHeight()
386: + rect2.getHeight());
387: if (dx > 0)
388: dx *= EPS;
389: else
390: dx = EPS;
391: if (dy > 0)
392: dy *= EPS;
393: else
394: dy = EPS;
395: return equalsEpsilon(rect1.getMinX(), rect2.getMinX(), dx)
396: && equalsEpsilon(rect1.getMinY(), rect2.getMinY(), dy)
397: && equalsEpsilon(rect1.getMaxX(), rect2.getMaxX(), dx)
398: && equalsEpsilon(rect1.getMaxY(), rect2.getMaxY(), dy);
399: }
400:
401: /**
402: * Compares the specified numbers with the specified tolerance.
403: */
404: private static boolean equalsEpsilon(final double v1,
405: final double v2, final double eps) {
406: return (Math.abs(v1 - v2) < eps)
407: || (java.lang.Double.doubleToLongBits(v1) == java.lang.Double
408: .doubleToLongBits(v2));
409: }
410:
411: /**
412: * Tests if the interior of this {@code Rectangle2D} entirely
413: * contains the specified set of rectangular coordinates.
414: *
415: * @param x the <var>x</var> coordinates of the upper left corner
416: * of the specified set of rectangular coordinates
417: * @param y the <var>y</var> coordinates of the upper left corner
418: * of the specified set of rectangular coordinates
419: * @param width the width of the specified set of rectangular coordinates
420: * @param height the height of the specified set of rectangular coordinates
421: * @return {@code true} if this {@code Rectangle2D}
422: * entirely contains specified set of rectangular
423: * coordinates; {@code false} otherwise.
424: */
425: public boolean contains(final double x, final double y,
426: final double width, final double height) {
427: if (!(xmin < xmax && ymin < ymax && width > 0 && height > 0)) {
428: return false;
429: } else {
430: return (x >= xmin && y >= ymin && (x + width) <= xmax && (y + height) <= ymax);
431: }
432: }
433:
434: /**
435: * Tests if the interior of this shape entirely contains the specified rectangle.
436: * This methods overrides the default {@link Rectangle2D} implementation in order
437: * to work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
438: * {@linkplain Double#NaN NaN} values.
439: *
440: * @param rect the specified rectangle.
441: * @return {@code true} if this shape entirely contains the specified rectangle.
442: */
443: public boolean contains(final Rectangle2D rect) {
444: if (!(xmin < xmax && ymin < ymax)) {
445: return false;
446: } else {
447: final double xmin2 = rect.getMinX();
448: final double xmax2 = rect.getMaxX();
449: if (!(xmax2 > xmin2))
450: return false;
451: final double ymin2 = rect.getMinY();
452: final double ymax2 = rect.getMaxY();
453: if (!(ymax2 > ymin2))
454: return false;
455: return (xmin2 >= xmin && ymin2 >= ymin && xmax2 <= xmax && ymax2 <= ymax);
456: }
457: }
458:
459: /**
460: * Tests if a specified coordinate is inside the boundary of this {@code Rectangle2D}.
461: *
462: * @param x the <var>x</var> coordinates to test.
463: * @param y the <var>y</var> coordinates to test.
464: * @return {@code true} if the specified coordinates are
465: * inside the boundary of this {@code Rectangle2D};
466: * {@code false} otherwise.
467: */
468: public boolean contains(final double x, final double y) {
469: return (x >= xmin && y >= ymin && x < xmax && y < ymax);
470: }
471:
472: /**
473: * Tests if the interior of the {@code inner} rectangle is contained in the interior
474: * and/or the edge of the {@code outter} rectangle. This method is similar to
475: * {@link #contains(Rectangle2D)} except for the following points:
476: * <ul>
477: * <li>This method doesn't test only the <em>interiors</em> of {@code outter}.
478: * It tests for the edges as well.</li>
479: * <li>This method tests also rectangle with zero {@linkplain Rectangle2D#getWidth width} or
480: * {@linkplain Rectangle2D#getHeight height} (which are {@linkplain Rectangle2D#isEmpty
481: * empty} according {@link Shape} contract).</li>
482: * <li>This method work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
483: * {@linkplain Double#NaN NaN} values.</li>
484: * </ul>
485: *
486: * This method is said <cite>inclusive</cite> because it tests bounds as closed interval
487: * rather then open interval (the default Java2D behavior). Usage of closed interval is
488: * required if at least one rectangle may be the bounding box of a perfectly horizontal
489: * or vertical line; such a bounding box has 0 width or height.
490: *
491: * @param outter The first rectangle to test.
492: * @param inner The second rectangle to test.
493: * @return {@code true} if the interior of {@code inner} is inside the interior
494: * and/or the edge of {@code outter}.
495: *
496: * @todo Check for negative width or height (should returns {@code false}).
497: */
498: public static boolean containsInclusive(final Rectangle2D outter,
499: final Rectangle2D inner) {
500: return outter.getMinX() <= inner.getMinX()
501: && outter.getMaxX() >= inner.getMaxX()
502: && outter.getMinY() <= inner.getMinY()
503: && outter.getMaxY() >= inner.getMaxY();
504: }
505:
506: /**
507: * Determines where the specified coordinates lie with respect
508: * to this {@code Rectangle2D}.
509: * This method computes a binary OR of the appropriate mask values
510: * indicating, for each side of this {@code Rectangle2D},
511: * whether or not the specified coordinates are on the same side
512: * of the edge as the rest of this {@code Rectangle2D}.
513: *
514: * @return the logical OR of all appropriate out codes.
515: *
516: * @see #OUT_LEFT
517: * @see #OUT_TOP
518: * @see #OUT_RIGHT
519: * @see #OUT_BOTTOM
520: */
521: public int outcode(final double x, final double y) {
522: int out = 0;
523: if (!(xmax > xmin))
524: out |= OUT_LEFT | OUT_RIGHT;
525: else if (x < xmin)
526: out |= OUT_LEFT;
527: else if (x > xmax)
528: out |= OUT_RIGHT;
529:
530: if (!(ymax > ymin))
531: out |= OUT_TOP | OUT_BOTTOM;
532: else if (y < ymin)
533: out |= OUT_TOP;
534: else if (y > ymax)
535: out |= OUT_BOTTOM;
536: return out;
537: }
538:
539: /**
540: * Returns a new {@code Rectangle2D} object representing the
541: * intersection of this {@code Rectangle2D} with the specified
542: * {@code Rectangle2D}.
543: *
544: * @param rect the {@code Rectangle2D} to be intersected with this {@code Rectangle2D}
545: * @return the largest {@code Rectangle2D} contained in both the specified
546: * {@code Rectangle2D} and in this {@code Rectangle2D}.
547: */
548: public Rectangle2D createIntersection(final Rectangle2D rect) {
549: final XRectangle2D r = new XRectangle2D();
550: r.xmin = Math.max(xmin, rect.getMinX());
551: r.ymin = Math.max(ymin, rect.getMinY());
552: r.xmax = Math.min(xmax, rect.getMaxX());
553: r.ymax = Math.min(ymax, rect.getMaxY());
554: return r;
555: }
556:
557: /**
558: * Returns a new {@code Rectangle2D} object representing the
559: * union of this {@code Rectangle2D} with the specified
560: * {@code Rectangle2D}.
561: *
562: * @param rect the {@code Rectangle2D} to be combined with
563: * this {@code Rectangle2D}
564: * @return the smallest {@code Rectangle2D} containing both
565: * the specified {@code Rectangle2D} and this
566: * {@code Rectangle2D}.
567: */
568: public Rectangle2D createUnion(final Rectangle2D rect) {
569: final XRectangle2D r = new XRectangle2D();
570: r.xmin = Math.min(xmin, rect.getMinX());
571: r.ymin = Math.min(ymin, rect.getMinY());
572: r.xmax = Math.max(xmax, rect.getMaxX());
573: r.ymax = Math.max(ymax, rect.getMaxY());
574: return r;
575: }
576:
577: /**
578: * Adds a point, specified by the double precision arguments
579: * {@code x} and {@code y}, to this {@code Rectangle2D}.
580: * The resulting {@code Rectangle2D} is the smallest {@code Rectangle2D}
581: * that contains both the original {@code Rectangle2D} and the specified point.
582: * <p>
583: * After adding a point, a call to {@code contains} with the
584: * added point as an argument does not necessarily return
585: * {@code true}. The {@code contains} method does not
586: * return {@code true} for points on the right or bottom
587: * edges of a rectangle. Therefore, if the added point falls on
588: * the left or bottom edge of the enlarged rectangle,
589: * {@code contains} returns {@code false} for that point.
590: */
591: public void add(final double x, final double y) {
592: if (x < xmin)
593: xmin = x;
594: if (x > xmax)
595: xmax = x;
596: if (y < ymin)
597: ymin = y;
598: if (y > ymax)
599: ymax = y;
600: }
601:
602: /**
603: * Adds a {@code Rectangle2D} object to this {@code Rectangle2D}.
604: * The resulting {@code Rectangle2D} is the union of the two
605: * {@code Rectangle2D} objects.
606: *
607: * @param rect the {@code Rectangle2D} to add to this {@code Rectangle2D}.
608: */
609: public void add(final Rectangle2D rect) {
610: double t;
611: if ((t = rect.getMinX()) < xmin)
612: xmin = t;
613: if ((t = rect.getMaxX()) > xmax)
614: xmax = t;
615: if ((t = rect.getMinY()) < ymin)
616: ymin = t;
617: if ((t = rect.getMaxY()) > ymax)
618: ymax = t;
619: }
620:
621: /**
622: * Returns the {@code String} representation of this {@code Rectangle2D}.
623: *
624: * @return a {@code String} representing this {@code Rectangle2D}.
625: */
626: public String toString() {
627: final StringBuffer buffer = new StringBuffer(Utilities
628: .getShortClassName(this ));
629: final NumberFormat format = NumberFormat.getNumberInstance();
630: final FieldPosition dummy = new FieldPosition(0);
631: buffer.append("[xmin=");
632: format.format(xmin, buffer, dummy);
633: buffer.append(" xmax=");
634: format.format(xmax, buffer, dummy);
635: buffer.append(" ymin=");
636: format.format(ymin, buffer, dummy);
637: buffer.append(" ymax=");
638: format.format(ymax, buffer, dummy);
639: buffer.append(']');
640: return buffer.toString();
641: }
642: }
|