org.geotoolkit.display.shape.XRectangle2D Maven / Gradle / Ivy
/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.display.shape;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import static java.lang.Double.doubleToLongBits;
import org.geotoolkit.util.converter.Classes;
/**
* Serializable rectangle capable to handle infinite values. Instead of using {@code x},
* {@code y}, {@code width} and {@code height} fields, this class uses {@link #xmin},
* {@link #xmax}, {@link #ymin} and {@link #ymax} fields. This choice provides two benefits:
*
*
* - Allows this class to works correctly with {@linkplain java.lang.Double#POSITIVE_INFINITY
* infinites} and {@linkplain java.lang.Double#NaN NaN} values (the width and
* height alternative has ambiguities).
* - Slightly faster {@code contains} and {@code intersects} methods since there is no
* addition or subtraction to perform.
*
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.10
*
* @since 1.2
* @module
*/
public class XRectangle2D extends Rectangle2D implements Serializable {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -1918221103635749436L;
/**
* 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 {@code getMinX()} and {@code getMinY()} methods return always
* {@linkplain java.lang.Double#NEGATIVE_INFINITY negative infinity} while the
* {@code getMaxX()} and {@code getMaxY()} methods return always
* {@linkplain java.lang.Double#POSITIVE_INFINITY positive infinity}.
*
* This rectangle can be given as argument to the {@linkplain #XRectangle2D(Rectangle2D)
* constructor} for initializing a new {@code XRectangle2D} to infinite bounds.
*/
public static final Rectangle2D INFINITY = new InfiniteRectangle2D();
/** Minimal x ordinate value. */ protected double xmin;
/** Minimal y ordinate value. */ protected double ymin;
/** Maximal x ordinate value. */ protected double xmax;
/** Maximal y ordinate value. */ protected double ymax;
/**
* Constructs a default rectangle initialized to {@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 with Java2D practice.
*
* @param x Minimal x ordinate value.
* @param y Minimal y ordinate value.
* @param width The rectangle width.
* @param height The rectangle height.
*/
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;
}
/**
* Constructs 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);
}
}
/**
* Creates a rectangle using maximal x and y values
* rather than width and height. This factory avoid the problem of NaN
* values when extremum are infinite numbers.
*
* @param xmin Minimal x ordinate value.
* @param ymin Minimal y ordinate value.
* @param xmax Maximal x ordinate value.
* @param ymax Maximal y ordinate value.
* @return A new rectangle initialized to the given bounds.
*/
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 this rectangle is empty. If this rectangle has at least one
* {@linkplain java.lang.Double#NaN NaN} value, then it is considered empty.
*
* @return {@code true} if this rectangle is empty; {@code false} otherwise.
*/
@Override
public boolean isEmpty() {
return !(xmin < xmax && ymin < ymax);
}
/**
* Returns the minimal x ordinate value.
*
* @return The minimal x ordinate value.
*/
@Override
public double getX() {
return xmin;
}
/**
* Returns the minimal y ordinate value.
*
* @return The minimal y ordinate value.
*/
@Override
public double getY() {
return ymin;
}
/**
* Returns the width of the rectangle.
*
* @return The width of the rectangle.
*/
@Override
public double getWidth() {
return xmax - xmin;
}
/**
* Returns the height of the rectangle.
*
* @return The height of the rectangle.
*/
@Override
public double getHeight() {
return ymax - ymin;
}
/**
* Returns the minimal x ordinate value.
*/
@Override
public double getMinX() {
return xmin;
}
/**
* Returns the minimal y ordinate value.
*/
@Override
public double getMinY() {
return ymin;
}
/**
* Returns the maximal x ordinate value.
*/
@Override
public double getMaxX() {
return xmax;
}
/**
* Returns the maximal y ordinate value.
*/
@Override
public double getMaxY() {
return ymax;
}
/**
* Returns the x ordinate of the center of the rectangle.
*/
@Override
public double getCenterX() {
return (xmin + xmax) * 0.5;
}
/**
* Returns the y ordinate of the center of the rectangle.
*/
@Override
public double getCenterY() {
return (ymin + ymax) * 0.5;
}
/**
* Sets the location and size of this rectangle to the specified values.
*
* @param x The x minimal ordinate value.
* @param y The y minimal ordinate value.
* @param width The rectangle width.
* @param height The rectangle height.
*/
@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 rectangle to be the same as the specified rectangle.
*
* @param r The rectangle to copy values from.
*/
@Override
public void setRect(final Rectangle2D r) {
this.xmin = r.getMinX();
this.ymin = r.getMinY();
this.xmax = r.getMaxX();
this.ymax = r.getMaxY();
}
/**
* Sets the framing rectangle to the given rectangle. The default implementation delegates
* to {@link #setRect(Rectangle2D)}. This is consistent with the default implementation of
* {@link #setFrame(double, double, double, double)}, which delegates to the corresponding
* method of {@link #setRect(double, double, double, double) setRect}.
*/
@Override
public void setFrame(final Rectangle2D r) {
setRect(r);
}
/**
* Tests if the interior of this rectangle intersects the interior of a
* specified set of rectangular coordinates.
*
* @param x The x minimal ordinate value.
* @param y The y minimal ordinate value.
* @param width The rectangle width.
* @param height The rectangle height.
* @return {@code true} if this rectangle intersects the interior of the
* 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 interior of this shape intersects the interior of a specified rectangle.
*
* @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:
*
*
* - This method doesn't test only if the interiors intersect.
* It tests for the edges as well.
* - 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.
* - This method work correctly with {@linkplain java.lang.Double#POSITIVE_INFINITY
* infinites} and {@linkplain java.lang.Double#NaN NaN} values.
*
*
* This method is said inclusive 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 method is similar to {@link Shape#intersects(Rectangle2D)}, except that
* it 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.
*
* This method is said inclusive 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.
*
* @param rect1 The first rectangle to test for equality.
* @param rect2 The second rectangle to test for equality.
* @return {@code true} if the rectangles are equal up to the tolerance value.
*
* @deprecated Use your own comparison method instead, so you can specify different
* tolerance threshold along different axis.
*/
@Deprecated
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.
*/
@Deprecated
private static boolean equalsEpsilon(final double v1, final double v2, final double eps) {
return (Math.abs(v1 - v2) < eps) || (doubleToLongBits(v1) == doubleToLongBits(v2));
}
/**
* Tests if the interior of this rectangle entirely
* contains the specified set of rectangular coordinates.
*
* @param x The x minimal ordinate value.
* @param y The y minimal ordinate value.
* @param width The rectangle width.
* @param height The rectangle height.
* @return {@code true} if this rectangle 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 java.lang.Double#POSITIVE_INFINITY infinites}
* and {@linkplain java.lang.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 x coordinates to test.
* @param y the y coordinates to test.
* @return {@code true} if the specified coordinates are inside the boundary of this
* rectangle, {@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:
*
*
* - This method doesn't test only the interiors of {@code outter}.
* It tests for the edges as well.
* - 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).
* - This method work correctly with {@linkplain java.lang.Double#POSITIVE_INFINITY
* infinites} and {@linkplain java.lang.Double#NaN NaN} values.
*
*
* This method is said inclusive 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 rectangle.
* 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
*/
@Override
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 rectangle with the specified one.
*
* @param rect The {@code Rectangle2D} to be intersected with this rectangle.
* @return The largest {@code Rectangle2D} contained in both the specified
* rectangle and this one.
*
* @see #intersect(Rectangle2D)
*/
@Override
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 rectangle with the specified one.
*
* @param rect The {@code Rectangle2D} to be combined with this rectangle.
* @return The smallest {@code Rectangle2D} containing both the specified
* {@code Rectangle2D} and this one.
*/
@Override
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 arguments {@code x} and {@code y}, to this rectangle.
* The resulting {@code Rectangle2D} is the smallest rectangle that contains both the
* original rectangle and the specified point.
*
* 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.
*
* @param x X ordinate value of the point to add.
* @param y Y ordinate value of the point to add.
*/
@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 rectangle.
* The resulting rectangle is the union of the two {@code Rectangle2D} objects.
*
* @param rect The {@code Rectangle2D} to add to this rectangle.
*/
@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;
}
/**
* Intersects a {@link Rectangle2D} object with this rectangle. The resulting
* rectangle is the intersection of the two {@code Rectangle2D} objects.
*
* Invoking this method is equivalent to invoking the following code, except
* that this method behaves correctly with infinite values.
*
* {@preformat java
* Rectangle2D.intersect(this, rect, this);
* }
*
* @param rect The {@code Rectangle2D} to intersect with this rectangle.
*
* @see #intersect(Rectangle2D, Rectangle2D, Rectangle2D)
* @see #createIntersection(Rectangle2D)
*
* @since 3.10
*/
public void intersect(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}.
* The ordinate order is (xmin, ymin,
* xmax, ymax), which is consistent
* with the {@link #createFromExtremums(double, double, double, double)} constructor
* and with the {@code BBOX} Well Known Text (WKT) syntax.
*
* @return a {@code String} representing this {@code Rectangle2D}.
*/
@Override
public String toString() {
return Classes.getShortClassName(this) +
"[xmin=" + xmin +
" ymin=" + ymin +
" xmax=" + xmax +
" ymax=" + ymax + ']';
}
}