/* * Copyright (c) 2016 Fraunhofer IGD * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Fraunhofer IGD <http://www.igd.fraunhofer.de/> */ package de.fhg.igd.geom; import java.io.Serializable; import de.fhg.igd.geom.util.BlochHashCode; import de.fhg.igd.geom.util.MathHelper; /** * This class represents an Extent in 2D, as defined by the lower left and upper * right corners. * * @author Thorsten Reitz */ public final class Extent implements Serializable, Localizable, Comparable<Extent>, Cloneable { /** * The class' serial version UID */ private static final long serialVersionUID = 7033392945091905596L; // member variables ........................................................ /** * lower left corner, x value. */ private double minX; /** * lower left corner, y value. */ private double minY; /** * upper right corner, x value. */ private double maxX; /** * upper right corner, y value. */ private double maxY; /** * Constructs a new infinitely negative Extent */ public Extent() { this.minX = Double.POSITIVE_INFINITY; this.minY = Double.POSITIVE_INFINITY; this.maxX = Double.NEGATIVE_INFINITY; this.maxY = Double.NEGATIVE_INFINITY; } /** * Full constructor with all parameters. * * @param minX lower left corner, x value. * @param minY lower left corner, y value. * @param maxX upper right corner, x value. * @param maxY upper right corner, y value. */ public Extent(double minX, double minY, double maxX, double maxY) { this.minX = minX; this.minY = minY; this.maxX = maxX; this.maxY = maxY; } /** * Creates an extent such that it spans both points. * * @param a the lower left corner * @param b the upper right corner */ public Extent(Point2D a, Point2D b) { this.minX = a.getX(); this.minY = a.getY(); this.maxX = b.getX(); this.maxY = b.getY(); normalize(); } /** * Copy constructor * * @param e The source extent */ public Extent(Extent e) { this(e.minX, e.minY, e.maxX, e.maxY); } // functional methods....................................................... /** * @param point the point that may be touched by this Extent * @return true if the specified Point2D touches this Extent. */ private boolean touches(Point2D point) { return touches(point.getX(), point.getY()); } /** * Test if a point, specified by two coordinates, touches this extent. * * @param x x coordinate of point to test * @param y x coordinate of point to test * @return true if the specified Point2D touches this Extent. */ private boolean touches(double x, double y) { // We keep the "unsafe" FP comparison since we prefer a proper touches() // over // something which cannot hold basic topological properties. That // touches isn't the most stable criteria is another thing. if (x == this.minX || x == this.maxX) { if (y <= this.maxY && y >= this.minY) { return true; } } if (y == this.minY || y == this.maxY) { if (x <= this.maxX && x >= this.minX) { return true; } } return false; } /** * @param ex the other extent * @return true if the given Extent has corners on this extent's edges. */ private boolean touchesHelper(Extent ex) { int touch_counter = 0; Point2D[] corner_points = new Point2D[4]; corner_points[0] = new Point2D(ex.minX, ex.minY); corner_points[1] = new Point2D(ex.minX, ex.maxY); corner_points[2] = new Point2D(ex.maxX, ex.minY); corner_points[3] = new Point2D(ex.maxX, ex.maxY); for (int i = 0; i < 4; i++) { if (this.touches(corner_points[i])) { touch_counter++; } } return touch_counter > 0 && touch_counter < 3; } /** * @param ex the other extent * @return true if the given Extent touches (and does NOT intersect) this * Extent. */ private boolean touches(Extent ex) { return (touchesHelper(ex) || ex.touchesHelper(this)) && !this.intersects(ex) && !this.covers(ex) && !ex.covers(this); } /** * @param ext the Extent that may have any relation to this Extent * @return true if the specified Extent has ANY spacial relation to this * Extent. */ public boolean any(Extent ext) { return (this.intersects(ext) || this.covers(ext) || ext.covers(this) || ext.equals(this) || ext.touches(this)); } /** * This method will return true if 1..2 corners of the given extent lie * within this Extent. If you want to check for all spatial relationships, * use intersectsOrCovers(Extent). It will also return true if the extents * have a cross-shaped intersections, that is, if no points lie in the other * extent but when the lines of the extent actually cut each other. * * @param ex the extent that may be intersected by this extent * @return true if this Extent intersects the given one */ private boolean intersects(Extent ex) { if (this.getMinX() >= ex.getMaxX() || this.getMaxX() <= ex.getMinX()) { return false; } else if (this.getMinY() >= ex.getMaxY() || this.getMaxY() <= ex.getMinY()) { return false; } return (!this.contains(ex) && !ex.contains(this) && !this.equals(ex)); } /** * This checks if this Extent covers the parameter extent. * * @param ex the Extent that may be covered by this Extent * @return true if this Extent covers the given one */ private boolean covers(Extent ex) { return (this.getMinX() <= ex.getMinX() && this.getMaxX() >= ex.getMaxX() && this.getMinY() <= ex.getMinY() && this.getMaxY() >= ex.getMaxY()); } /** * This checks if this Extent completely contains the parameter extent. * * @param ex the Extent that may be contained by this Extent * @return true if this Extent contains the given one */ private boolean contains(Extent ex) { return (this.getMinX() < ex.getMinX() && this.getMaxX() > ex.getMaxX() && this.getMinY() < ex.getMinY() && this.getMaxY() > ex.getMaxY()); } /** * @return the width (delta of minx and maxX) of this Extent. */ public double getWidth() { return Math.abs(this.getMaxX() - this.getMinX()); } /** * @return the height (delta of minY and maxY) of this Extent. */ public double getHeight() { return Math.abs(this.getMaxY() - this.getMinY()); } // canonical java methods .................................................. /** * Two extents are defined as being equal when their LL and UR coordinates * are equal. * * @param o the extent to compare to * @return true if both extents are equal */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (o != null && o instanceof Extent) { Extent other = (Extent) o; return (this.getMinX() == other.getMinX() && this.getMinY() == other.getMinY() && this.getMaxX() == other.getMaxX() && this.getMaxY() == other.getMaxY()); } return false; } /** * checks if min* and max* are actually in the expected relation. If a pair * is real and in the wrong order, it is swapped. */ private void normalize() { if (MathHelper.isReal(minX) && MathHelper.isReal(maxX) && maxX < minX) { double tmp = minX; minX = maxX; maxX = tmp; } if (MathHelper.isReal(minY) && MathHelper.isReal(maxY) && maxY < minY) { double tmp = minY; minY = maxY; maxY = tmp; } } /** * Provides a hashCode so that x.hashCode() == y.hashCode() when x.equals(y) * == true * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int hash = BlochHashCode.HASH_CONSTANT; hash = BlochHashCode.addFieldToHash(hash, this.getMinX()); hash = BlochHashCode.addFieldToHash(hash, this.getMinY()); hash = BlochHashCode.addFieldToHash(hash, this.getMaxX()); return BlochHashCode.addFieldToHash(hash, this.getMaxY()); } /** * @see Object#toString() */ @Override public String toString() { StringBuilder buffer = new StringBuilder(80); buffer.append("Extent["); buffer.append("maxX = ").append(maxX); buffer.append(" maxY = ").append(maxY); buffer.append(" minX = ").append(minX); buffer.append(" minY = ").append(minY); buffer.append("]"); return buffer.toString(); } @Override public BoundingBox getBoundingBox() { return new BoundingBox(this.minX, this.minY, 0d, this.maxX, this.maxY, 0d); } // getter / setter methods ................................................. /** * Access method for the minX property. * * @return the current value of the minX property */ public double getMinX() { return minX; } /** * Sets the value of the minX property. * * @param aMinX the new value of the minX property */ public void setMinX(double aMinX) { minX = aMinX; } /** * Access method for the minY property. * * @return the current value of the minY property */ public double getMinY() { return minY; } /** * Sets the value of the minY property. * * @param aMinY the new value of the minY property */ public void setMinY(double aMinY) { minY = aMinY; } /** * Access method for the maxX property. * * @return the current value of the maxX property */ public double getMaxX() { return maxX; } /** * Sets the value of the maxX property. * * @param aMaxX the new value of the maxX property */ public void setMaxX(double aMaxX) { maxX = aMaxX; } /** * Access method for the maxY property. * * @return the current value of the maxY property */ public double getMaxY() { return maxY; } /** * Sets the value of the maxY property. * * @param aMaxY the new value of the maxY property */ public void setMaxY(double aMaxY) { maxY = aMaxY; } /** * @return the point at the minimum coordinate on this extent boundary */ public Point2D getMin() { return new Point2D(minX, minY); } /** * @return the point at the maximum coordinate on this extent boundary */ public Point2D getMax() { return new Point2D(maxX, maxY); } /** * Compares the area of this extent to another one * * @param e the other extent to compare to * @return -1, 0, 1 if the area of this extent is greater than, equal to or * less than the other one. */ @Override public int compareTo(Extent e) { double a1 = this.getWidth() * this.getHeight(); double a2 = e.getWidth() * e.getHeight(); if (a1 < a2) { return -1; } else if (a1 > a2) { return 1; } return 0; } /** * @see Object#clone() */ @Override public Object clone() { return new Extent(this.minX, this.minY, this.maxX, this.maxY); } }