/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2006 Vivid Solutions * (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.geometry.iso.topograph2D; import java.io.Serializable; import org.opengis.geometry.Geometry; /** * Defines a rectangular region of the 2D coordinate plane. It is often used to * represent the bounding box of a {@link Geometry}, e.g. the minimum and * maximum x and y values of the {@link Coordinate}s. * <p> * Note that Envelopes support infinite or half-infinite regions, by using the * values of <code>Double.POSITIVE_INFINITY</code> and * <code>Double.NEGATIVE_INFINITY</code>. * <p> * When Envelope objects are created or initialized, the supplies extent values * are automatically sorted into the correct order. * * * * @source $URL$ */ public class Envelope implements Serializable { public int hashCode() { // Algorithm from Effective Java by Joshua Bloch [Jon Aquino] int result = 17; result = 37 * result + Coordinate.hashCode(minx); result = 37 * result + Coordinate.hashCode(maxx); result = 37 * result + Coordinate.hashCode(miny); result = 37 * result + Coordinate.hashCode(maxy); return result; } // USED!!! /** * Test the point q to see whether it intersects the Envelope defined by * p1-p2 * * @param p1 * one extremal point of the envelope * @param p2 * another extremal point of the envelope * @param q * the point to test for intersection * @return <code>true</code> if q intersects the envelope p1-p2 */ public static boolean intersects(Coordinate p1, Coordinate p2, Coordinate q) { // OptimizeIt shows that Math#min and Math#max here are a bottleneck. // Replace with direct comparisons. [Jon Aquino] if (((q.x >= (p1.x < p2.x ? p1.x : p2.x)) && (q.x <= (p1.x > p2.x ? p1.x : p2.x))) && ((q.y >= (p1.y < p2.y ? p1.y : p2.y)) && (q.y <= (p1.y > p2.y ? p1.y : p2.y)))) { return true; } return false; } /** * Test the envelope defined by p1-p2 for intersection with the envelope * defined by q1-q2 * * @param p1 * one extremal point of the envelope P * @param p2 * another extremal point of the envelope P * @param q1 * one extremal point of the envelope Q * @param q2 * another extremal point of the envelope Q * @return <code>true</code> if Q intersects P */ public static boolean intersects(Coordinate p1, Coordinate p2, Coordinate q1, Coordinate q2) { double minq = Math.min(q1.x, q2.x); double maxq = Math.max(q1.x, q2.x); double minp = Math.min(p1.x, p2.x); double maxp = Math.max(p1.x, p2.x); if (minp > maxq) return false; if (maxp < minq) return false; minq = Math.min(q1.y, q2.y); maxq = Math.max(q1.y, q2.y); minp = Math.min(p1.y, p2.y); maxp = Math.max(p1.y, p2.y); if (minp > maxq) return false; if (maxp < minq) return false; return true; } /** * the minimum x-coordinate */ private double minx; /** * the maximum x-coordinate */ private double maxx; /** * the minimum y-coordinate */ private double miny; /** * the maximum y-coordinate */ private double maxy; /** * Creates a null <code>Envelope</code>. */ public Envelope() { init(); } /** * Creates an <code>Envelope</code> for a region defined by maximum and * minimum values. * * @param x1 * the first x-value * @param x2 * the second x-value * @param y1 * the first y-value * @param y2 * the second y-value */ public Envelope(double x1, double x2, double y1, double y2) { init(x1, x2, y1, y2); } /** * Creates an <code>Envelope</code> for a region defined by two * Coordinates. * * @param p1 * the first Coordinate * @param p2 * the second Coordinate */ public Envelope(Coordinate p1, Coordinate p2) { init(p1, p2); } /** * Creates an <code>Envelope</code> for a region defined by a single * Coordinate. * * @param p1 * the Coordinate */ public Envelope(Coordinate p) { init(p); } /** * Create an <code>Envelope</code> from an existing Envelope. * * @param env * the Envelope to initialize from */ public Envelope(Envelope env) { init(env); } /** * Initialize to a null <code>Envelope</code>. */ public void init() { setToNull(); } /** * Initialize an <code>Envelope</code> for a region defined by maximum and * minimum values. * * @param x1 * the first x-value * @param x2 * the second x-value * @param y1 * the first y-value * @param y2 * the second y-value */ public void init(double x1, double x2, double y1, double y2) { if (x1 < x2) { minx = x1; maxx = x2; } else { minx = x2; maxx = x1; } if (y1 < y2) { miny = y1; maxy = y2; } else { miny = y2; maxy = y1; } } /** * Initialize an <code>Envelope</code> to a region defined by two * Coordinates. * * @param p1 * the first Coordinate * @param p2 * the second Coordinate */ public void init(Coordinate p1, Coordinate p2) { init(p1.x, p2.x, p1.y, p2.y); } /** * Initialize an <code>Envelope</code> to a region defined by a single * Coordinate. * * @param p1 * the first Coordinate * @param p2 * the second Coordinate */ public void init(Coordinate p) { init(p.x, p.x, p.y, p.y); } /** * Initialize an <code>Envelope</code> from an existing Envelope. * * @param env * the Envelope to initialize from */ public void init(Envelope env) { this.minx = env.minx; this.maxx = env.maxx; this.miny = env.miny; this.maxy = env.maxy; } /** * Makes this <code>Envelope</code> a "null" envelope, that is, the * envelope of the empty geometry. */ public void setToNull() { minx = 0; maxx = -1; miny = 0; maxy = -1; } /** * Returns <code>true</code> if this <code>Envelope</code> is a "null" * envelope. * * @return <code>true</code> if this <code>Envelope</code> is * uninitialized or is the envelope of the empty geometry. */ public boolean isNull() { return maxx < minx; } /** * Returns the difference between the maximum and minimum x values. * * @return max x - min x, or 0 if this is a null <code>Envelope</code> */ public double getWidth() { if (isNull()) { return 0; } return maxx - minx; } /** * Returns the difference between the maximum and minimum y values. * * @return max y - min y, or 0 if this is a null <code>Envelope</code> */ public double getHeight() { if (isNull()) { return 0; } return maxy - miny; } /** * Returns the <code>Envelope</code>s minimum x-value. min x > max x * indicates that this is a null <code>Envelope</code>. * * @return the minimum x-coordinate */ public double getMinX() { return minx; } /** * Returns the <code>Envelope</code>s maximum x-value. min x > max x * indicates that this is a null <code>Envelope</code>. * * @return the maximum x-coordinate */ public double getMaxX() { return maxx; } /** * Returns the <code>Envelope</code>s minimum y-value. min y > max y * indicates that this is a null <code>Envelope</code>. * * @return the minimum y-coordinate */ public double getMinY() { return miny; } /** * Returns the <code>Envelope</code>s maximum y-value. min y > max y * indicates that this is a null <code>Envelope</code>. * * @return the maximum y-coordinate */ public double getMaxY() { return maxy; } /** * Enlarges the boundary of the <code>Envelope</code> so that it contains * (x,y). Does nothing if (x,y) is already on or within the boundaries. * * @param x * the value to lower the minimum x to or to raise the maximum x * to * @param y * the value to lower the minimum y to or to raise the maximum y * to */ public void expandToInclude(Coordinate p) { expandToInclude(p.x, p.y); } /** * Expands this envelope by a given distance in all directions. Both * positive and negative distances are supported. * * @param distance * the distance to expand the envelope * @return this envelope */ public void expandBy(double distance) { expandBy(distance, distance); } /** * Expands this envelope by a given distance in all directions. Both * positive and negative distances are supported. * * @param deltaX * the distance to expand the envelope along the the X axis * @param deltaY * the distance to expand the envelope along the the Y axis */ public void expandBy(double deltaX, double deltaY) { if (isNull()) return; minx -= deltaX; maxx += deltaX; miny -= deltaY; maxy += deltaY; // check for envelope disappearing if (minx > maxx || miny > maxy) setToNull(); } /** * Enlarges the boundary of the <code>Envelope</code> so that it contains * (x,y). Does nothing if (x,y) is already on or within the boundaries. * * @param x * the value to lower the minimum x to or to raise the maximum x * to * @param y * the value to lower the minimum y to or to raise the maximum y * to */ public void expandToInclude(double x, double y) { if (isNull()) { minx = x; maxx = x; miny = y; maxy = y; } else { if (x < minx) { minx = x; } if (x > maxx) { maxx = x; } if (y < miny) { miny = y; } if (y > maxy) { maxy = y; } } } /** * Enlarges the boundary of the <code>Envelope</code> so that it contains * <code>other</code>. Does nothing if <code>other</code> is wholly on * or within the boundaries. * * @param other * the <code>Envelope</code> to merge with */ public void expandToInclude(Envelope other) { if (other.isNull()) { return; } if (isNull()) { minx = other.getMinX(); maxx = other.getMaxX(); miny = other.getMinY(); maxy = other.getMaxY(); } else { if (other.minx < minx) { minx = other.minx; } if (other.maxx > maxx) { maxx = other.maxx; } if (other.miny < miny) { miny = other.miny; } if (other.maxy > maxy) { maxy = other.maxy; } } } /** * Translates this envelope by given amounts in the X and Y direction. * * @param transX * the amount to translate along the X axis * @param transY * the amount to translate along the Y axis */ public void translate(double transX, double transY) { if (isNull()) { return; } init(getMinX() + transX, getMaxX() + transX, getMinY() + transY, getMaxY() + transY); } /** * Computes the coordinate of the centre of this envelope (as long as it is * non-null * * @return the centre coordinate of this envelope <code>null</code> if the * envelope is null */ public Coordinate centre() { if (isNull()) return null; return new Coordinate((getMinX() + getMaxX()) / 2.0, (getMinY() + getMaxY()) / 2.0); } /** * Computes the intersection of two {@link Envelope}s * * @param env * the envelope to intersect with * @return a new Envelope representing the intersection of the envelopes * (this will be the null envelope if either argument is null, or * they do not intersect) */ public Envelope intersection(Envelope env) { if (isNull() || env.isNull() || !intersects(env)) return new Envelope(); double intMinX = minx > env.minx ? minx : env.minx; double intMinY = miny > env.miny ? miny : env.miny; double intMaxX = maxx < env.maxx ? maxx : env.maxx; double intMaxY = maxy < env.maxy ? maxy : env.maxy; return new Envelope(intMinX, intMaxX, intMinY, intMaxY); } // USED !!! /** * Returns <code>true</code> if the given point lies in or on the * envelope. * * @param p * the point which this <code>Envelope</code> is being checked * for containing * @return <code>true</code> if the point lies in the interior or on the * boundary of this <code>Envelope</code>. */ public boolean contains(Coordinate p) { return contains(p.x, p.y); } /** * Returns <code>true</code> if the given point lies in or on the * envelope. * * @param x * the x-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @param y * the y-coordinate of the point which this <code>Envelope</code> * is being checked for containing * @return <code>true</code> if <code>(x, y)</code> lies in the interior * or on the boundary of this <code>Envelope</code>. */ public boolean contains(double x, double y) { return x >= minx && x <= maxx && y >= miny && y <= maxy; } /** * Check if the region defined by <code>other</code> overlaps (intersects) * the region of this <code>Envelope</code>. * * @param other * the <code>Envelope</code> which this <code>Envelope</code> * is being checked for overlapping * @return <code>true</code> if the <code>Envelope</code>s overlap */ public boolean intersects(Envelope other) { if (isNull() || other.isNull()) { return false; } return !(other.minx > maxx || other.maxx < minx || other.miny > maxy || other.maxy < miny); } /** * @deprecated Use #intersects instead. In the future, #overlaps may be * changed to be a true overlap check; that is, whether the * intersection is two-dimensional. */ public boolean overlaps(Envelope other) { return intersects(other); } /** * Check if the point <code>p</code> overlaps (lies inside) the region of * this <code>Envelope</code>. * * @param other * the <code>Coordinate</code> to be tested * @return <code>true</code> if the point overlaps this * <code>Envelope</code> */ public boolean intersects(Coordinate p) { return intersects(p.x, p.y); } /** * @deprecated Use #intersects instead. */ public boolean overlaps(Coordinate p) { return intersects(p); } /** * Check if the point <code>(x, y)</code> overlaps (lies inside) the * region of this <code>Envelope</code>. * * @param x * the x-ordinate of the point * @param y * the y-ordinate of the point * @return <code>true</code> if the point overlaps this * <code>Envelope</code> */ public boolean intersects(double x, double y) { return !(x > maxx || x < minx || y > maxy || y < miny); } /** * @deprecated Use #intersects instead. */ public boolean overlaps(double x, double y) { return intersects(x, y); } /** * Returns <code>true</code> if the <code>Envelope other</code> lies * wholely inside this <code>Envelope</code> (inclusive of the boundary). * * @param other * the <code>Envelope</code> which this <code>Envelope</code> * is being checked for containing * @return <code>true</code> if <code>other</code> is contained in this * <code>Envelope</code> */ public boolean contains(Envelope other) { if (isNull() || other.isNull()) { return false; } return other.getMinX() >= minx && other.getMaxX() <= maxx && other.getMinY() >= miny && other.getMaxY() <= maxy; } /** * Computes the distance between this and another <code>Envelope</code>. * The distance between overlapping Envelopes is 0. Otherwise, the distance * is the Euclidean distance between the closest points. */ public double distance(Envelope env) { if (intersects(env)) return 0; double dx = 0.0; if (maxx < env.minx) dx = env.minx - maxx; if (minx > env.maxx) dx = minx - env.maxx; double dy = 0.0; if (maxy < env.miny) dy = env.miny - maxy; if (miny > env.maxy) dy = miny - env.maxy; // if either is zero, the envelopes overlap either vertically or // horizontally if (dx == 0.0) return dy; if (dy == 0.0) return dx; return Math.sqrt(dx * dx + dy * dy); } public boolean equals(Object other) { if (!(other instanceof Envelope)) { return false; } Envelope otherEnvelope = (Envelope) other; if (isNull()) { return otherEnvelope.isNull(); } return maxx == otherEnvelope.getMaxX() && maxy == otherEnvelope.getMaxY() && minx == otherEnvelope.getMinX() && miny == otherEnvelope.getMinY(); } public String toString() { return "Env[" + minx + " : " + maxx + ", " + miny + " : " + maxy + "]"; } }