/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-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.resources.geometry;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.io.Serializable;
import org.geotools.resources.Classes;
/**
* Serializable, high-performance double-precision rectangle. Instead of using
* {@code x}, {@code y}, {@code width} and {@code height},
* this class store rectangle's coordinates into the following fields:
* {@link #xmin}, {@link #xmax}, {@link #ymin} et {@link #ymax}. Methods likes
* {@code contains} and {@code intersects} are faster, which make this
* class more appropriate for using intensively inside a loop. Furthermore, this
* class work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
* {@linkplain Double#NaN NaN} values.
*
* @since 2.0
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class XRectangle2D extends Rectangle2D implements Serializable {
/**
* A small number for testing intersection between an arbitrary shape and a rectangle.
*/
private static final double EPS = 1E-6;
/**
* An immutable instance of a {@link Rectangle2D} with bounds extending toward
* infinities. The {@link #getMinX} and {@link #getMinY} methods return always
* {@link java.lang.Double#NEGATIVE_INFINITY}, while the {@link #getMaxX} and
* {@link #getMaxY} methods return always {@link java.lang.Double#POSITIVE_INFINITY}.
* This rectangle can be used as argument in the {@link XRectangle2D} constructor for
* initializing a new {@code XRectangle2D} to infinite bounds.
*/
public static final Rectangle2D INFINITY = InfiniteRectangle2D.INFINITY;
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -1918221103635749436L;
/** Minimal <var>x</var> coordinate. */ protected double xmin;
/** Minimal <var>y</var> coordinate. */ protected double ymin;
/** Maximal <var>x</var> coordinate. */ protected double xmax;
/** Maximal <var>y</var> coordinate. */ protected double ymax;
/**
* Construct a default rectangle. Initial coordinates are {@code (0,0,0,0)}.
*/
public XRectangle2D() {
}
/**
* Construct a rectangle with the specified location and dimension.
* This constructor uses the same signature than {@link Rectangle2D} for consistency.
*/
public XRectangle2D(final double x, final double y, final double width, final double height) {
this.xmin = x;
this.ymin = y;
this.xmax = x+width;
this.ymax = y+height;
}
/**
* Construct a rectangle with the same coordinates than the supplied rectangle.
*
* @param rect The rectangle, or {@code null} in none (in which case this constructor
* is equivalents to the no-argument constructor). Use {@link #INFINITY} for
* initializing this {@code XRectangle2D} with infinite bounds.
*/
public XRectangle2D(final Rectangle2D rect) {
if (rect != null) {
setRect(rect);
}
}
/**
* Create a rectangle using maximal <var>x</var> and <var>y</var> values
* rather than width and height. This factory avoid the problem of NaN
* values when extremums are infinite numbers.
*/
public static XRectangle2D createFromExtremums(final double xmin, final double ymin,
final double xmax, final double ymax)
{
final XRectangle2D rect = new XRectangle2D();
rect.xmin = xmin;
rect.ymin = ymin;
rect.xmax = xmax;
rect.ymax = ymax;
return rect;
}
/**
* Determines whether the {@code RectangularShape} is empty.
* When the {@code RectangularShape} is empty, it encloses no
* area.
*
* @return {@code true} if the {@code RectangularShape} is empty;
* {@code false} otherwise.
*/
public boolean isEmpty() {
return !(xmin<xmax && ymin<ymax);
}
/**
* Returns the X coordinate of the upper left corner of
* the framing rectangle in {@code double} precision.
*
* @return the x coordinate of the upper left corner of the framing rectangle.
*/
public double getX() {
return xmin;
}
/**
* Returns the Y coordinate of the upper left corner of
* the framing rectangle in {@code double} precision.
*
* @return the y coordinate of the upper left corner of the framing rectangle.
*/
public double getY() {
return ymin;
}
/**
* Returns the width of the framing rectangle in
* {@code double} precision.
* @return the width of the framing rectangle.
*/
public double getWidth() {
return xmax-xmin;
}
/**
* Returns the height of the framing rectangle in {@code double} precision.
*
* @return the height of the framing rectangle.
*/
public double getHeight() {
return ymax-ymin;
}
/**
* Returns the smallest X coordinate of the rectangle.
*/
@Override
public double getMinX() {
return xmin;
}
/**
* Returns the smallest Y coordinate of the rectangle.
*/
@Override
public double getMinY() {
return ymin;
}
/**
* Returns the largest X coordinate of the rectangle.
*/
@Override
public double getMaxX() {
return xmax;
}
/**
* Returns the largest Y coordinate of the rectangle.
*/
@Override
public double getMaxY() {
return ymax;
}
/**
* Returns the X coordinate of the center of the rectangle.
*/
@Override
public double getCenterX() {
return (xmin + xmax) * 0.5;
}
/**
* Returns the Y coordinate of the center of the rectangle.
*/
@Override
public double getCenterY() {
return (ymin + ymax) * 0.5;
}
/**
* Sets the location and size of this {@code Rectangle2D}
* to the specified double values.
*
* @param x the <var>x</var> coordinates to which to set the
* location of the upper left corner of this {@code Rectangle2D}
* @param y the <var>y</var> coordinates to which to set the
* location of the upper left corner of this {@code Rectangle2D}
* @param width the value to use to set the width of this {@code Rectangle2D}
* @param height the value to use to set the height of this {@code Rectangle2D}
*/
@Override
public void setRect(final double x, final double y, final double width, final double height) {
this.xmin = x;
this.ymin = y;
this.xmax = x + width;
this.ymax = y + height;
}
/**
* Sets this {@code Rectangle2D} to be the same as the
* specified {@code Rectangle2D}.
*
* @param r the specified {@code Rectangle2D}
*/
@Override
public void setRect(final Rectangle2D r) {
this.xmin = r.getMinX();
this.ymin = r.getMinY();
this.xmax = r.getMaxX();
this.ymax = r.getMaxY();
}
/**
* Tests if the interior of this {@code Rectangle2D}
* intersects the interior of a specified set of rectangular
* coordinates.
*
* @param x the <var>x</var> coordinates of the upper left corner
* of the specified set of rectangular coordinates
* @param y the <var>y</var> coordinates of the upper left corner
* of the specified set of rectangular coordinates
* @param width the width of the specified set of rectangular coordinates
* @param height the height of the specified set of rectangular coordinates
* @return {@code true} if this {@code Rectangle2D}
* intersects the interior of a specified set of rectangular
* coordinates; {@code false} otherwise.
*/
@Override
public boolean intersects(final double x, final double y,
final double width, final double height)
{
if (!(xmin<xmax && ymin<ymax && width>0 && height>0)) {
return false;
} else {
return (x<xmax && y<ymax && x+width>xmin && y+height>ymin);
}
}
/**
* Tests if the <strong>interior</strong> of this shape intersects the
* <strong>interior</strong> of a specified rectangle. This methods overrides the default
* {@link Rectangle2D} implementation in order to work correctly with
* {@linkplain Double#POSITIVE_INFINITY infinites} and {@linkplain Double#NaN NaN} values.
*
* @param rect the specified rectangle.
* @return {@code true} if this shape and the specified rectangle intersect each other.
*
* @see #intersectInclusive(Rectangle2D, Rectangle2D)
*/
@Override
public boolean intersects(final Rectangle2D rect) {
if (!(xmin<xmax && ymin<ymax)) {
return false;
} else {
final double xmin2 = rect.getMinX();
final double xmax2 = rect.getMaxX(); if (!(xmax2 > xmin2)) return false;
final double ymin2 = rect.getMinY();
final double ymax2 = rect.getMaxY(); if (!(ymax2 > ymin2)) return false;
return (xmin2<xmax && ymin2<ymax && xmax2>xmin && ymax2>ymin);
}
}
/**
* Tests if the interior and/or the edge of two rectangles intersect. This method
* is similar to {@link #intersects(Rectangle2D)} except for the following points:
* <ul>
* <li>This method doesn't test only if the <em>interiors</em> intersect.
* It tests for the edges as well.</li>
* <li>This method tests also rectangle with zero {@linkplain Rectangle2D#getWidth width} or
* {@linkplain Rectangle2D#getHeight height} (which are {@linkplain Rectangle2D#isEmpty
* empty} according {@link Shape} contract). However, rectangle with negative width or
* height are still considered as empty.</li>
* <li>This method work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
* {@linkplain Double#NaN NaN} values.</li>
* </ul>
*
* This method is said <cite>inclusive</cite> because it tests bounds as closed interval
* rather then open interval (the default Java2D behavior). Usage of closed interval is
* required if at least one rectangle may be the bounding box of a perfectly horizontal
* or vertical line; such a bounding box has 0 width or height.
*
* @param rect1 The first rectangle to test.
* @param rect2 The second rectangle to test.
* @return {@code true} if the interior and/or the edge of the two specified rectangles
* intersects.
*/
public static boolean intersectInclusive(final Rectangle2D rect1, final Rectangle2D rect2) {
final double xmin1 = rect1.getMinX();
final double xmax1 = rect1.getMaxX(); if (!(xmax1 >= xmin1)) return false;
final double ymin1 = rect1.getMinY();
final double ymax1 = rect1.getMaxY(); if (!(ymax1 >= ymin1)) return false;
final double xmin2 = rect2.getMinX();
final double xmax2 = rect2.getMaxX(); if (!(xmax2 >= xmin2)) return false;
final double ymin2 = rect2.getMinY();
final double ymax2 = rect2.getMaxY(); if (!(ymax2 >= ymin2)) return false;
return (xmax2 >= xmin1 &&
ymax2 >= ymin1 &&
xmin2 <= xmax1 &&
ymin2 <= ymax1);
}
/**
* Tests if the interior of the {@code Shape} intersects the interior of a specified
* rectangle. This method might conservatively return {@code true} when there is a high
* probability that the rectangle and the shape intersect, but the calculations to accurately
* determine this intersection are prohibitively expensive. This is similar to
* {@link Shape#intersects(Rectangle2D)}, except that this method tests also rectangle with
* zero {@linkplain Rectangle2D#getWidth width} or {@linkplain Rectangle2D#getHeight height}
* (which are {@linkplain Rectangle2D#isEmpty empty} according {@link Shape} contract). However,
* rectangle with negative width or height are still considered as empty.
* <br><br>
* This method is said <cite>inclusive</cite> because it try to mimic
* {@link #intersectInclusive(Rectangle2D, Rectangle2D)} behavior, at
* least for rectangle with zero width or height.
*
* @param shape The shape.
* @param rect The rectangle to test for inclusion.
* @return {@code true} if the interior of the shape and the interior of the specified
* rectangle intersect, or are both highly likely to intersect.
*/
public static boolean intersectInclusive(final Shape shape, final Rectangle2D rect) {
double x = rect.getX();
double y = rect.getY();
double width = rect.getWidth();
double height = rect.getHeight();
if(width == 0 && height == 0) {
width = EPS;
height = EPS;
} else if (width == 0) {
width = height*EPS;
x -= 0.5*width;
} else if (height == 0) {
height = width*EPS;
y -= 0.5*height;
}
return shape.intersects(x, y, width, height);
}
/**
* Returns {@code true} if the two rectangles are equals up to an epsilon value.
*/
public static boolean equalsEpsilon(final Rectangle2D rect1, final Rectangle2D rect2) {
double dx = 0.5 * Math.abs(rect1.getWidth() + rect2.getWidth());
double dy = 0.5 * Math.abs(rect1.getHeight() + rect2.getHeight());
if (dx > 0) dx *= EPS; else dx = EPS;
if (dy > 0) dy *= EPS; else dy = EPS;
return equalsEpsilon(rect1.getMinX(), rect2.getMinX(), dx) &&
equalsEpsilon(rect1.getMinY(), rect2.getMinY(), dy) &&
equalsEpsilon(rect1.getMaxX(), rect2.getMaxX(), dx) &&
equalsEpsilon(rect1.getMaxY(), rect2.getMaxY(), dy);
}
/**
* Compares the specified numbers with the specified tolerance.
*/
private static boolean equalsEpsilon(final double v1, final double v2, final double eps) {
return (Math.abs(v1 - v2) < eps) ||
(java.lang.Double.doubleToLongBits(v1) == java.lang.Double.doubleToLongBits(v2));
}
/**
* Tests if the interior of this {@code Rectangle2D} entirely
* contains the specified set of rectangular coordinates.
*
* @param x the <var>x</var> coordinates of the upper left corner
* of the specified set of rectangular coordinates
* @param y the <var>y</var> coordinates of the upper left corner
* of the specified set of rectangular coordinates
* @param width the width of the specified set of rectangular coordinates
* @param height the height of the specified set of rectangular coordinates
* @return {@code true} if this {@code Rectangle2D}
* entirely contains specified set of rectangular
* coordinates; {@code false} otherwise.
*/
@Override
public boolean contains(final double x, final double y,
final double width, final double height)
{
if (!(xmin<xmax && ymin<ymax && width>0 && height>0)) {
return false;
} else {
return (x>=xmin && y>=ymin && (x+width)<=xmax && (y+height)<=ymax);
}
}
/**
* Tests if the interior of this shape entirely contains the specified rectangle.
* This methods overrides the default {@link Rectangle2D} implementation in order
* to work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
* {@linkplain Double#NaN NaN} values.
*
* @param rect the specified rectangle.
* @return {@code true} if this shape entirely contains the specified rectangle.
*/
@Override
public boolean contains(final Rectangle2D rect) {
if (!(xmin<xmax && ymin<ymax)) {
return false;
} else {
final double xmin2 = rect.getMinX();
final double xmax2 = rect.getMaxX(); if (!(xmax2 > xmin2)) return false;
final double ymin2 = rect.getMinY();
final double ymax2 = rect.getMaxY(); if (!(ymax2 > ymin2)) return false;
return (xmin2>=xmin && ymin2>=ymin && xmax2<=xmax && ymax2<=ymax);
}
}
/**
* Tests if a specified coordinate is inside the boundary of this {@code Rectangle2D}.
*
* @param x the <var>x</var> coordinates to test.
* @param y the <var>y</var> coordinates to test.
* @return {@code true} if the specified coordinates are
* inside the boundary of this {@code Rectangle2D};
* {@code false} otherwise.
*/
@Override
public boolean contains(final double x, final double y) {
return (x>=xmin && y>=ymin && x<xmax && y<ymax);
}
/**
* Tests if the interior of the {@code inner} rectangle is contained in the interior
* and/or the edge of the {@code outter} rectangle. This method is similar to
* {@link #contains(Rectangle2D)} except for the following points:
* <ul>
* <li>This method doesn't test only the <em>interiors</em> of {@code outter}.
* It tests for the edges as well.</li>
* <li>This method tests also rectangle with zero {@linkplain Rectangle2D#getWidth width} or
* {@linkplain Rectangle2D#getHeight height} (which are {@linkplain Rectangle2D#isEmpty
* empty} according {@link Shape} contract).</li>
* <li>This method work correctly with {@linkplain Double#POSITIVE_INFINITY infinites} and
* {@linkplain Double#NaN NaN} values.</li>
* </ul>
*
* This method is said <cite>inclusive</cite> because it tests bounds as closed interval
* rather then open interval (the default Java2D behavior). Usage of closed interval is
* required if at least one rectangle may be the bounding box of a perfectly horizontal
* or vertical line; such a bounding box has 0 width or height.
*
* @param outter The first rectangle to test.
* @param inner The second rectangle to test.
* @return {@code true} if the interior of {@code inner} is inside the interior
* and/or the edge of {@code outter}.
*
* @todo Check for negative width or height (should returns {@code false}).
*/
public static boolean containsInclusive(final Rectangle2D outter, final Rectangle2D inner) {
return outter.getMinX() <= inner.getMinX() && outter.getMaxX() >= inner.getMaxX() &&
outter.getMinY() <= inner.getMinY() && outter.getMaxY() >= inner.getMaxY();
}
/**
* Determines where the specified coordinates lie with respect
* to this {@code Rectangle2D}.
* This method computes a binary OR of the appropriate mask values
* indicating, for each side of this {@code Rectangle2D},
* whether or not the specified coordinates are on the same side
* of the edge as the rest of this {@code Rectangle2D}.
*
* @return the logical OR of all appropriate out codes.
*
* @see #OUT_LEFT
* @see #OUT_TOP
* @see #OUT_RIGHT
* @see #OUT_BOTTOM
*/
public int outcode(final double x, final double y) {
int out=0;
if (!(xmax > xmin)) out |= OUT_LEFT | OUT_RIGHT;
else if (x < xmin) out |= OUT_LEFT;
else if (x > xmax) out |= OUT_RIGHT;
if (!(ymax > ymin)) out |= OUT_TOP | OUT_BOTTOM;
else if (y < ymin) out |= OUT_TOP;
else if (y > ymax) out |= OUT_BOTTOM;
return out;
}
/**
* Returns a new {@code Rectangle2D} object representing the
* intersection of this {@code Rectangle2D} with the specified
* {@code Rectangle2D}.
*
* @param rect the {@code Rectangle2D} to be intersected with this {@code Rectangle2D}
* @return the largest {@code Rectangle2D} contained in both the specified
* {@code Rectangle2D} and in this {@code Rectangle2D}.
*/
public Rectangle2D createIntersection(final Rectangle2D rect) {
final XRectangle2D r=new XRectangle2D();
r.xmin = Math.max(xmin, rect.getMinX());
r.ymin = Math.max(ymin, rect.getMinY());
r.xmax = Math.min(xmax, rect.getMaxX());
r.ymax = Math.min(ymax, rect.getMaxY());
return r;
}
/**
* Returns a new {@code Rectangle2D} object representing the
* union of this {@code Rectangle2D} with the specified
* {@code Rectangle2D}.
*
* @param rect the {@code Rectangle2D} to be combined with
* this {@code Rectangle2D}
* @return the smallest {@code Rectangle2D} containing both
* the specified {@code Rectangle2D} and this
* {@code Rectangle2D}.
*/
public Rectangle2D createUnion(final Rectangle2D rect) {
final XRectangle2D r=new XRectangle2D();
r.xmin = Math.min(xmin, rect.getMinX());
r.ymin = Math.min(ymin, rect.getMinY());
r.xmax = Math.max(xmax, rect.getMaxX());
r.ymax = Math.max(ymax, rect.getMaxY());
return r;
}
/**
* Adds a point, specified by the double precision arguments
* {@code x} and {@code y}, to this {@code Rectangle2D}.
* The resulting {@code Rectangle2D} is the smallest {@code Rectangle2D}
* that contains both the original {@code Rectangle2D} and the specified point.
* <p>
* After adding a point, a call to {@code contains} with the
* added point as an argument does not necessarily return
* {@code true}. The {@code contains} method does not
* return {@code true} for points on the right or bottom
* edges of a rectangle. Therefore, if the added point falls on
* the left or bottom edge of the enlarged rectangle,
* {@code contains} returns {@code false} for that point.
*/
@Override
public void add(final double x, final double y) {
if (x < xmin) xmin = x;
if (x > xmax) xmax = x;
if (y < ymin) ymin = y;
if (y > ymax) ymax = y;
}
/**
* Adds a {@code Rectangle2D} object to this {@code Rectangle2D}.
* The resulting {@code Rectangle2D} is the union of the two
* {@code Rectangle2D} objects.
*
* @param rect the {@code Rectangle2D} to add to this {@code Rectangle2D}.
*/
@Override
public void add(final Rectangle2D rect) {
double t;
if ((t=rect.getMinX()) < xmin) xmin = t;
if ((t=rect.getMaxX()) > xmax) xmax = t;
if ((t=rect.getMinY()) < ymin) ymin = t;
if ((t=rect.getMaxY()) > ymax) ymax = t;
}
/**
* Returns the {@code String} representation of this {@code Rectangle2D}.
*
* @return a {@code String} representing this {@code Rectangle2D}.
*/
@Override
public String toString() {
final StringBuffer buffer = new StringBuffer(Classes.getShortClassName(this));
final NumberFormat format = NumberFormat.getNumberInstance();
final FieldPosition dummy = new FieldPosition(0);
buffer.append("[xmin="); format.format(xmin, buffer, dummy);
buffer.append(" xmax="); format.format(xmax, buffer, dummy);
buffer.append(" ymin="); format.format(ymin, buffer, dummy);
buffer.append(" ymax="); format.format(ymax, buffer, dummy);
buffer.append(']');
return buffer.toString();
}
}