ucar.unidata.geoloc.ProjectionRect Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.unidata.geoloc;
import com.google.common.math.DoubleMath;
import ucar.nc2.util.Misc;
import java.io.*;
/**
* Bounding box for ProjectionPoint's.
* Note that getX() getY() really means getMinX(), getMinY(), rather than
* "upper left point" of the rectangle.
*
* @author John Caron
*/
public class ProjectionRect implements java.io.Serializable {
private double x, y, width, height;
/**
* default constructor, initialized to center (0,0) and width (10000, 10000)
*/
public ProjectionRect() {
this(-5000, -5000, 5000, 5000);
}
/**
* Construct a ProjectionRect from any two opposite corner points.
*
* @param corner1 a corner.
* @param corner2 the opposite corner.
*/
public ProjectionRect(ProjectionPoint corner1, ProjectionPoint corner2) {
this(corner1.getX(), corner1.getY(), corner2.getX(), corner2.getY());
}
/**
* Construct a ProjectionRect from any two opposite corner points.
*
* @param minimum lower left corner, ie the minimum x and y
* @param width x width.
* @param height y height
*/
public ProjectionRect(ProjectionPoint minimum, double width, double height) {
setRect(minimum.getX(), minimum.getY(), width, height);
}
/**
* Copy Constructor
*
* @param r rectangle to copy
*/
public ProjectionRect(ProjectionRect r) {
this(r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY());
}
/**
* construct a MapArea from any two opposite corner points
*
* @param x1 x coord of any corner of the bounding box
* @param y1 y coord of the same corner as x1
* @param x2 x coord of opposite corner from x1,y1
* @param y2 y coord of same corner as x2
*/
public ProjectionRect(double x1, double y1, double x2, double y2) {
double wx0 = 0.5 * (x1 + x2);
double wy0 = 0.5 * (y1 + y2);
double width = Math.abs(x1 - x2);
double height = Math.abs(y1 - y2);
setRect(wx0 - width / 2, wy0 - height / 2, width, height);
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getWidth() {
return width;
}
public double getHeight() {
return height;
}
////////////////////////////////////////////////////
// taken from java.awt.geom.Rectangle2D, removed because awt missing on android
/**
* Returns the smallest X coordinate of the framing
* rectangle of the Shape
in double
* precision.
*
* @return the smallest X coordinate of the framing
* rectangle of the Shape
.
* @since 1.2
*/
public double getMinX() {
return getX();
}
/**
* Returns the smallest Y coordinate of the framing
* rectangle of the Shape
in double
* precision.
*
* @return the smallest Y coordinate of the framing
* rectangle of the Shape
.
* @since 1.2
*/
public double getMinY() {
return getY();
}
/**
* Returns the largest X coordinate of the framing
* rectangle of the Shape
in double
* precision.
*
* @return the largest X coordinate of the framing
* rectangle of the Shape
.
* @since 1.2
*/
public double getMaxX() {
return getX() + getWidth();
}
/**
* Returns the largest Y coordinate of the framing
* rectangle of the Shape
in double
* precision.
*
* @return the largest Y coordinate of the framing
* rectangle of the Shape
.
* @since 1.2
*/
public double getMaxY() {
return getY() + getHeight();
}
/**
* Returns the X coordinate of the center of the framing
* rectangle of the Shape
in double
* precision.
* @return the X coordinate of the center of the framing rectangle
* of the Shape
.
* @since 1.2
*/
public double getCenterX() {
return getX() + getWidth() / 2.0;
}
/**
* Returns the Y coordinate of the center of the framing
* rectangle of the Shape
in double
* precision.
* @return the Y coordinate of the center of the framing rectangle
* of the Shape
.
* @since 1.2
*/
public double getCenterY() {
return getY() + getHeight() / 2.0;
}
/**
* Adds a Rectangle2D
object to this
* Rectangle2D
. The resulting Rectangle2D
* is the union of the two Rectangle2D
objects.
* @param r the Rectangle2D
to add to this
* Rectangle2D
.
* @since 1.2
*/
public void add(ProjectionRect r) {
double x1 = Math.min(getMinX(), r.getMinX());
double x2 = Math.max(getMaxX(), r.getMaxX());
double y1 = Math.min(getMinY(), r.getMinY());
double y2 = Math.max(getMaxY(), r.getMaxY());
setRect(x1, y1, x2 - x1, y2 - y1);
}
/**
* Adds a point, specified by the double precision arguments
* newx
and newy
, to this
* Rectangle2D
. The resulting Rectangle2D
* is the smallest Rectangle2D
that
* contains both the original Rectangle2D
and the
* specified point.
*
* After adding a point, a call to contains
with the
* added point as an argument does not necessarily return
* true
. The contains
method does not
* return 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,
* contains
returns false
for that point.
* @param newx the X coordinate of the new point
* @param newy the Y coordinate of the new point
* @since 1.2
*/
public void add(double newx, double newy) {
double x1 = Math.min(getMinX(), newx);
double x2 = Math.max(getMaxX(), newx);
double y1 = Math.min(getMinY(), newy);
double y2 = Math.max(getMaxY(), newy);
setRect(x1, y1, x2 - x1, y2 - y1);
}
/**
* Adds the Point2D
object pt
to this
* Rectangle2D
.
* The resulting Rectangle2D
is the smallest
* Rectangle2D
that contains both the original
* Rectangle2D
and the specified Point2D
.
*
* After adding a point, a call to contains
with the
* added point as an argument does not necessarily return
* true
. The contains
* method does not return 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,
* contains
returns false
for that point.
* @param pt the new Point2D
to add to this
* Rectangle2D
.
* @since 1.2
*/
public void add(ProjectionPoint pt) {
add(pt.getX(), pt.getY());
}
public boolean isEmpty() {
return (width <= 0.0) || (height <= 0.0);
}
public boolean intersects(ProjectionRect r) {
return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public boolean intersects(double x, double y, double w, double h) {
if (isEmpty() || w <= 0 || h <= 0) {
return false;
}
double x0 = getX();
double y0 = getY();
return (x + w > x0 &&
y + h > y0 &&
x < x0 + getWidth() &&
y < y0 + getHeight());
}
/**
* Intersects the pair of specified source Rectangle2D
* objects and puts the result into the specified destination
* Rectangle2D
object. One of the source rectangles
* can also be the destination to avoid creating a third Rectangle2D
* object, but in this case the original points of this source
* rectangle will be overwritten by this method.
* @param src1 the first of a pair of Rectangle2D
* objects to be intersected with each other
* @param src2 the second of a pair of Rectangle2D
* objects to be intersected with each other
* @param dest the Rectangle2D
that holds the
* results of the intersection of src1
and
* src2
* @since 1.2
*/
public static void intersect(ProjectionRect src1, ProjectionRect src2, ProjectionRect dest) {
double x1 = Math.max(src1.getMinX(), src2.getMinX());
double y1 = Math.max(src1.getMinY(), src2.getMinY());
double x2 = Math.min(src1.getMaxX(), src2.getMaxX());
double y2 = Math.min(src1.getMaxY(), src2.getMaxY());
dest.setRect(x1, y1, x2 - x1, y2 - y1);
}
/**
* Returns {@code true} if this bounding box contains {@code point}.
*
* @param point a point in projection coordinates.
* @return {@code true} if this bounding box contains {@code point}.
*/
public boolean contains(ProjectionPoint point) {
return DoubleMath.fuzzyCompare(point.getX(), getMinX(), 1e-6) >= 0 &&
DoubleMath.fuzzyCompare(point.getX(), getMaxX(), 1e-6) <= 0 &&
DoubleMath.fuzzyCompare(point.getY(), getMinY(), 1e-6) >= 0 &&
DoubleMath.fuzzyCompare(point.getY(), getMaxY(), 1e-6) <= 0;
}
/**
*
* Returns {@code true} if this bounding box contains {@code rect}.
*
* @param rect a bounding box in projection coordinates
* @return {@code true} if this bounding box contains {@code rect}.
*/
public boolean contains(ProjectionRect rect) {
boolean contained = false;
// The old ProjectionRect class was based off of java.awt.Rectangle.
// If the rectangles were the same, .contains(rect) returned true.
// This check makes sure the old behavior is preserved.
if (this.equals(rect)) {
contained = true;
} else {
// Just check to see if corners of rect contained within this
contained = (this.contains(rect.getMinPoint()) && this.contains(rect.getMaxPoint()));
}
return contained;
}
/////////////////////////////////////////////////////
/**
* Get the Lower Right Point
*
* @return the Lower Right Point
*/
public ProjectionPoint getLowerRightPoint() {
return new ProjectionPointImpl(getMaxPoint().getX(), getMinPoint().getY());
}
/**
* Get the Upper Left Point (same as getMaxPoint)
*
* @return the Upper Left Point
*/
public ProjectionPoint getUpperRightPoint() {
return getMaxPoint();
}
/**
* Get the Lower Right Point (same as getMinPoint)
*
* @return the Lower Right Point
*/
public ProjectionPoint getLowerLeftPoint() {
return getMinPoint();
}
/**
* Get the Upper Left Point
*
* @return the Upper Left Point
*/
public ProjectionPoint getUpperLeftPoint() {
return new ProjectionPointImpl(getMinPoint().getX(),
getMaxPoint().getY());
}
/**
* Get the minimum corner of the bounding box.
*
* @return minimum corner of the bounding box
*/
public ProjectionPoint getMinPoint() {
return new ProjectionPointImpl(getX(), getY());
}
/**
* Get the maximum corner of the bounding box.
*
* @return maximum corner of the bounding box
*/
public ProjectionPoint getMaxPoint() {
return new ProjectionPointImpl(getX() + getWidth(),
getY() + getHeight());
}
/**
* Get a String representation of this object.
*
* @return a String representation of this object.
*/
public String toString() {
return String.format("min: %.3f %.3f size: %.3f %.3f", getX(), getY(), getWidth(), getHeight());
}
public String toString2(int ndec) {
String f = " %."+ndec+"f";
return String.format("min:"+f+f+" max:"+f+f, getX(), getY(), getMaxX(), getMaxY());
}
/**
* set minimum X
*
* @param x minimum x
*/
public void setX(double x) {
setRect(x, getY(), getWidth(), getHeight());
}
/**
* set minimum Y
*
* @param y minimum y
*/
public void setY(double y) {
setRect(getX(), y, getWidth(), getHeight());
}
/**
* set X width
*
* @param w x width
*/
public void setWidth(double w) {
setRect(getX(), getY(), w, getHeight());
}
/**
* set Y height
*
* @param h Y height
*/
public void setHeight(double h) {
setRect(getX(), getY(), getWidth(), h);
}
public void setRect(ProjectionRect r) {
setRect(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public void setRect(double x, double y, double w, double h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
// serialization
/**
* Read the object from the input stream of the serialized object
*
* @param s stream to read
* @throws ClassNotFoundException couldn't file the class
* @throws IOException Problem reading from stream
*/
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
double x = s.readDouble();
double y = s.readDouble();
double w = s.readDouble();
double h = s.readDouble();
setRect(x, y, w, h);
}
/**
* Wrtie the object to the output stream
*
* @param s stream to write
* @throws IOException Problem writing to stream
*/
private void writeObject(ObjectOutputStream s) throws IOException {
s.writeDouble(getX());
s.writeDouble(getY());
s.writeDouble(getWidth());
s.writeDouble(getHeight());
}
// Exact comparison is needed in order to be consistent with hashCode().
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProjectionRect that = (ProjectionRect) o;
if (Double.compare(that.height, height) != 0) return false;
if (Double.compare(that.width, width) != 0) return false;
if (Double.compare(that.x, x) != 0) return false;
if (Double.compare(that.y, y) != 0) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(x);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(width);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(height);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
/**
* Returns the result of {@link #nearlyEquals(ProjectionRect, double)}, with {@link Misc#defaultMaxRelativeDiffFloat}.
*/
public boolean nearlyEquals(ProjectionRect other) {
return nearlyEquals(other, Misc.defaultMaxRelativeDiffFloat);
}
/**
* Returns {@code true} if this rectangle is nearly equal to {@code other}. The "near equality" of corners is
* determined using {@link ProjectionPoint#nearlyEquals(ProjectionPoint, double)}, with the specified maxRelDiff.
*
* @param other the other rectangle to check.
* @param maxRelDiff the maximum {@link Misc#relativeDifference relative difference} that two corners may have.
* @return {@code true} if this rectangle is nearly equal to {@code other}.
*/
public boolean nearlyEquals(ProjectionRect other, double maxRelDiff) {
return this.getLowerLeftPoint() .nearlyEquals(other.getLowerLeftPoint(), maxRelDiff) &&
this.getUpperRightPoint().nearlyEquals(other.getUpperRightPoint(), maxRelDiff);
}
}