001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2004, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.geometry;
018:
019: // J2SE dependencies
020: import java.awt.geom.Rectangle2D;
021:
022: // OpenGIS dependencies
023: import org.opengis.util.Cloneable;
024: import org.opengis.geometry.DirectPosition;
025: import org.opengis.geometry.Envelope;
026: import org.opengis.geometry.MismatchedDimensionException;
027: import org.opengis.geometry.MismatchedReferenceSystemException;
028: import org.opengis.referencing.crs.CoordinateReferenceSystem;
029: import org.opengis.referencing.cs.AxisDirection; // For javadoc
030:
031: // Geotools dependencies
032: import org.geotools.resources.Utilities;
033: import org.geotools.resources.i18n.Errors;
034: import org.geotools.resources.i18n.ErrorKeys;
035:
036: /**
037: * A two-dimensional envelope on top of {@link Rectangle2D}. This implementation is provided for
038: * interoperability between Java2D and GeoAPI.
039: * <p>
040: * <strong>Note:</strong> This class inherits {@linkplain #x x} and {@linkplain #y y} fields. But
041: * despite their names, they don't need to be oriented toward {@linkplain AxisDirection#EAST East}
042: * and {@linkplain AxisDirection#NORTH North} respectively. The (<var>x</var>,<var>y</var>) axis
043: * can have any orientation and should be understood as "ordinate 0" and "ordinate 1" values
044: * instead. This is not specific to this implementation; in Java2D too, the visual axis orientation
045: * depend on the {@linkplain java.awt.Graphics2D#getTransform affine transform in the graphics
046: * context}.
047: *
048: * @since 2.1
049: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/geometry/Envelope2D.java $
050: * @version $Id: Envelope2D.java 26137 2007-07-03 17:59:44Z desruisseaux $
051: * @author Martin Desruisseaux
052: *
053: * @see GeneralEnvelope
054: * @see org.geotools.geometry.jts.ReferencedEnvelope
055: * @see org.opengis.metadata.extent.GeographicBoundingBox
056: */
057: public class Envelope2D extends Rectangle2D.Double implements Envelope,
058: Cloneable {
059: /**
060: * Serial number for interoperability with different versions.
061: */
062: private static final long serialVersionUID = -3319231220761419350L;
063:
064: /**
065: * The coordinate reference system, or {@code null}.
066: */
067: private CoordinateReferenceSystem crs;
068:
069: /**
070: * Constructs two-dimensional envelope defined by an other {@link Envelope}.
071: */
072: public Envelope2D(final Envelope envelope) {
073: super (envelope.getMinimum(0), envelope.getMinimum(1), envelope
074: .getLength(0), envelope.getLength(1));
075:
076: // TODO: check below should be first, if only Sun could fix RFE #4093999.
077: final int dimension = envelope.getDimension();
078: if (dimension != 2) {
079: throw new MismatchedDimensionException(Errors.format(
080: ErrorKeys.NOT_TWO_DIMENSIONAL_$1, new Integer(
081: dimension)));
082: }
083: setCoordinateReferenceSystem(envelope
084: .getCoordinateReferenceSystem());
085: }
086:
087: /**
088: * Constructs two-dimensional envelope defined by an other {@link Rectangle2D}.
089: */
090: public Envelope2D(final CoordinateReferenceSystem crs,
091: final Rectangle2D rect) {
092: super (rect.getX(), rect.getY(), rect.getWidth(), rect
093: .getHeight());
094: setCoordinateReferenceSystem(crs);
095: }
096:
097: /**
098: * Constructs two-dimensional envelope defined by the specified coordinates. Despite
099: * their name, the (<var>x</var>,<var>y</var>) coordinates don't need to be oriented
100: * toward ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North}).
101: * Those parameter names simply match the {@linkplain #x x} and {@linkplain #y y} fields.
102: * The actual axis orientations are determined by the specified CRS.
103: * See the {@linkplain Envelope2D class javadoc} for details.
104: */
105: public Envelope2D(final CoordinateReferenceSystem crs,
106: final double x, final double y, final double width,
107: final double height) {
108: super (x, y, width, height);
109: setCoordinateReferenceSystem(crs);
110: }
111:
112: /**
113: * Constructs two-dimensional envelope defined by the specified coordinates. Despite
114: * their name, the (<var>x</var>,<var>y</var>) coordinates don't need to be oriented
115: * toward ({@linkplain AxisDirection#EAST East}, {@linkplain AxisDirection#NORTH North}).
116: * Those parameter names simply match the {@linkplain #x x} and {@linkplain #y y} fields.
117: * The actual axis orientations are determined by the specified CRS.
118: * See the {@linkplain Envelope2D class javadoc} for details.
119: * <p>
120: * The {@code minDP} and {@code maxDP} arguments usually contains the minimal and maximal
121: * ordinate values respectively, but this is not mandatory. The ordinates will be rearanged
122: * as needed.
123: *
124: * @param minDP The fist position.
125: * @param maxDP The second position.
126: * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS.
127: *
128: * @since 2.4
129: */
130: public Envelope2D(final DirectPosition2D minDP,
131: final DirectPosition2D maxDP)
132: throws MismatchedReferenceSystemException {
133: // Uncomment next lines if Sun fixes RFE #4093999
134: // ensureNonNull("minDP", minDP);
135: // ensureNonNull("maxDP", maxDP);
136: super (Math.min(minDP.x, maxDP.x), Math.min(minDP.y, maxDP.y),
137: Math.abs(maxDP.x - minDP.x), Math
138: .abs(maxDP.y - minDP.y));
139: setCoordinateReferenceSystem(AbstractEnvelope
140: .getCoordinateReferenceSystem(minDP, maxDP));
141: }
142:
143: /**
144: * Returns the coordinate reference system in which the coordinates are given.
145: *
146: * @return The coordinate reference system, or {@code null}.
147: */
148: public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
149: return crs;
150: }
151:
152: /**
153: * Set the coordinate reference system in which the coordinate are given.
154: *
155: * @param crs The new coordinate reference system, or {@code null}.
156: */
157: public void setCoordinateReferenceSystem(
158: final CoordinateReferenceSystem crs) {
159: AbstractDirectPosition.checkCoordinateReferenceSystemDimension(
160: crs, getDimension());
161: this .crs = crs;
162: }
163:
164: /**
165: * Returns the number of dimensions.
166: */
167: public final int getDimension() {
168: return 2;
169: }
170:
171: /**
172: * A coordinate position consisting of all the minimal ordinates for each
173: * dimension for all points within the {@code Envelope}.
174: *
175: * @return The lower corner.
176: *
177: * @todo Change the return type to {@link DirectPosition2D} when we will
178: * be allowed to compile for J2SE 1.5.
179: */
180: public DirectPosition getLowerCorner() {
181: return new DirectPosition2D(crs, getMinX(), getMinY());
182: }
183:
184: /**
185: * A coordinate position consisting of all the maximal ordinates for each
186: * dimension for all points within the {@code Envelope}.
187: *
188: * @return The upper corner.
189: *
190: * @todo Change the return type to {@link DirectPosition2D} when we will
191: * be allowed to compile for J2SE 1.5.
192: */
193: public DirectPosition getUpperCorner() {
194: return new DirectPosition2D(crs, getMaxX(), getMaxY());
195: }
196:
197: /**
198: * Returns the minimal ordinate along the specified dimension.
199: */
200: public final double getMinimum(final int dimension) {
201: switch (dimension) {
202: case 0:
203: return getMinX();
204: case 1:
205: return getMinY();
206: default:
207: throw new ArrayIndexOutOfBoundsException(dimension);
208: }
209: }
210:
211: /**
212: * Returns the maximal ordinate along the specified dimension.
213: */
214: public final double getMaximum(final int dimension) {
215: switch (dimension) {
216: case 0:
217: return getMaxX();
218: case 1:
219: return getMaxY();
220: default:
221: throw new ArrayIndexOutOfBoundsException(dimension);
222: }
223: }
224:
225: /**
226: * Returns the center ordinate along the specified dimension.
227: */
228: public final double getCenter(final int dimension) {
229: switch (dimension) {
230: case 0:
231: return getCenterX();
232: case 1:
233: return getCenterY();
234: default:
235: throw new ArrayIndexOutOfBoundsException(dimension);
236: }
237: }
238:
239: /**
240: * Returns the envelope length along the specified dimension.
241: * This length is equals to the maximum ordinate minus the
242: * minimal ordinate.
243: */
244: public final double getLength(final int dimension) {
245: switch (dimension) {
246: case 0:
247: return getWidth();
248: case 1:
249: return getHeight();
250: default:
251: throw new ArrayIndexOutOfBoundsException(dimension);
252: }
253: }
254:
255: /**
256: * Returns a hash value for this envelope. This value need not remain consistent between
257: * different implementations of the same class.
258: */
259: public int hashCode() {
260: int code = super .hashCode() ^ (int) serialVersionUID;
261: if (crs != null) {
262: code += crs.hashCode();
263: }
264: return code;
265: }
266:
267: /**
268: * Compares the specified object with this envelope for equality.
269: */
270: public boolean equals(final Object object) {
271: if (super .equals(object)) {
272: final CoordinateReferenceSystem otherCRS = (object instanceof Envelope2D) ? ((Envelope2D) object).crs
273: : null;
274: return Utilities.equals(crs, otherCRS);
275: }
276: return false;
277: }
278:
279: /**
280: * Returns {@code true} if {@code this} envelope bounds is equals to {@code that} envelope
281: * bounds in two specified dimensions. The coordinate reference system is not compared, since
282: * it doesn't need to have the same number of dimensions.
283: *
284: * @param that The envelope to compare to.
285: * @param xDim The dimension of {@code that} envelope to compare to the <var>x</var> dimension
286: * of {@code this} envelope.
287: * @param yDim The dimension of {@code that} envelope to compare to the <var>y</var> dimension
288: * of {@code this} envelope.
289: * @param eps A small tolerance number for floating point number comparaisons. This value will
290: * be scaled according this envelope {@linkplain #width width} and
291: * {@linkplain #height height}.
292: * @return {@code true} if the envelope bounds are the same (up to the specified tolerance
293: * level) in the specified dimensions, or {@code false} otherwise.
294: */
295: public boolean boundsEquals(final Envelope that, final int xDim,
296: final int yDim, double eps) {
297: eps *= 0.5 * (width + height);
298: for (int i = 0; i < 4; i++) {
299: final int dim2D = (i & 1);
300: final int dimND = (dim2D == 0) ? xDim : yDim;
301: final double value2D, valueND;
302: if ((i & 2) == 0) {
303: value2D = this .getMinimum(dim2D);
304: valueND = that.getMinimum(dimND);
305: } else {
306: value2D = this .getMaximum(dim2D);
307: valueND = that.getMaximum(dimND);
308: }
309: // Use '!' for catching NaN values.
310: if (!(Math.abs(value2D - valueND) <= eps)) {
311: return false;
312: }
313: }
314: return true;
315: }
316:
317: /**
318: * Returns a string representation of this envelope. The default implementation is okay
319: * for occasional formatting (for example for debugging purpose). But if there is a lot
320: * of envelopes to format, users will get more control by using their own instance of
321: * {@link org.geotools.measure.CoordinateFormat}.
322: *
323: * @since 2.4
324: */
325: public String toString() {
326: return AbstractEnvelope.toString(this);
327: }
328: }
|