0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
0005: * (C) 2004, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation;
0010: * version 2.1 of the License.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: */
0017: package org.geotools.geometry;
0018:
0019: // J2SE dependencies
0020: import java.awt.geom.Rectangle2D;
0021: import java.io.Serializable;
0022: import java.util.Arrays;
0023: import javax.units.Unit;
0024: import javax.units.ConversionException;
0025:
0026: // OpenGIS dependencies
0027: import org.opengis.util.Cloneable;
0028: import org.opengis.coverage.grid.GridRange;
0029: import org.opengis.metadata.extent.GeographicBoundingBox;
0030: import org.opengis.referencing.datum.PixelInCell;
0031: import org.opengis.referencing.operation.MathTransform;
0032: import org.opengis.referencing.operation.TransformException;
0033: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0034: import org.opengis.geometry.DirectPosition;
0035: import org.opengis.geometry.Envelope;
0036: import org.opengis.geometry.MismatchedDimensionException;
0037: import org.opengis.geometry.MismatchedReferenceSystemException;
0038:
0039: // Geotools dependencies
0040: import org.geotools.referencing.CRS;
0041: import org.geotools.resources.Utilities;
0042: import org.geotools.resources.i18n.Errors;
0043: import org.geotools.resources.i18n.ErrorKeys;
0044: import org.geotools.resources.geometry.XRectangle2D;
0045: import org.geotools.referencing.crs.DefaultGeographicCRS;
0046:
0047: /**
0048: * A minimum bounding box or rectangle. Regardless of dimension, an {@code Envelope} can
0049: * be represented without ambiguity as two {@linkplain DirectPosition direct positions}
0050: * (coordinate points). To encode an {@code Envelope}, it is sufficient to encode these
0051: * two points.
0052: * <p>
0053: * This particular implementation of {@code Envelope} is said "General" because it
0054: * uses coordinates of an arbitrary dimension.
0055: * <p>
0056: * <strong>Tip:</strong> The metadata package provides a
0057: * {@link org.opengis.metadata.extent.GeographicBoundingBox}, which can be used as
0058: * a kind of envelope with a coordinate reference system fixed to WGS 84 (EPSG:4326).
0059: *
0060: * @since 2.0
0061: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/geometry/GeneralEnvelope.java $
0062: * @version $Id: GeneralEnvelope.java 26151 2007-07-04 18:54:48Z desruisseaux $
0063: * @author Martin Desruisseaux
0064: * @author Simone Giannecchini
0065: *
0066: * @see Envelope2D
0067: * @see org.geotools.geometry.jts.ReferencedEnvelope
0068: * @see org.opengis.metadata.extent.GeographicBoundingBox
0069: */
0070: public class GeneralEnvelope extends AbstractEnvelope implements
0071: Cloneable, Serializable {
0072: /**
0073: * Serial number for interoperability with different versions.
0074: */
0075: private static final long serialVersionUID = 1752330560227688940L;
0076:
0077: /**
0078: * Minimum and maximum ordinate values. The first half contains minimum
0079: * ordinates, while the last half contains maximum ordinates. Consider
0080: * this reference as final; it is modified by {@link #clone} only.
0081: */
0082: private double[] ordinates;
0083:
0084: /**
0085: * The coordinate reference system, or {@code null}.
0086: */
0087: private CoordinateReferenceSystem crs;
0088:
0089: /**
0090: * Constructs an empty envelope of the specified dimension.
0091: * All ordinates are initialized to 0 and the coordinate reference
0092: * system is undefined.
0093: */
0094: public GeneralEnvelope(final int dimension) {
0095: ordinates = new double[dimension * 2];
0096: }
0097:
0098: /**
0099: * Constructs one-dimensional envelope defined by a range of values.
0100: *
0101: * @param min The minimal value.
0102: * @param max The maximal value.
0103: */
0104: public GeneralEnvelope(final double min, final double max) {
0105: ordinates = new double[] { min, max };
0106: checkCoherence();
0107: }
0108:
0109: /**
0110: * Constructs a envelope defined by two positions.
0111: *
0112: * @param minDP Minimum ordinate values.
0113: * @param maxDP Maximum ordinate values.
0114: * @throws MismatchedDimensionException if the two positions don't have the same dimension.
0115: * @throws IllegalArgumentException if an ordinate value in the minimum point is not
0116: * less than or equal to the corresponding ordinate value in the maximum point.
0117: */
0118: public GeneralEnvelope(final double[] minDP, final double[] maxDP)
0119: throws IllegalArgumentException {
0120: ensureNonNull("minDP", minDP);
0121: ensureNonNull("maxDP", maxDP);
0122: ensureSameDimension(minDP.length, maxDP.length);
0123: ordinates = new double[minDP.length + maxDP.length];
0124: System.arraycopy(minDP, 0, ordinates, 0, minDP.length);
0125: System.arraycopy(maxDP, 0, ordinates, minDP.length,
0126: maxDP.length);
0127: checkCoherence();
0128: }
0129:
0130: /**
0131: * Constructs a envelope defined by two positions. The coordinate
0132: * reference system is inferred from the supplied direct position.
0133: *
0134: * @param minDP Point containing minimum ordinate values.
0135: * @param maxDP Point containing maximum ordinate values.
0136: * @throws MismatchedDimensionException if the two positions don't have the same dimension.
0137: * @throws MismatchedReferenceSystemException if the two positions don't use the same CRS.
0138: * @throws IllegalArgumentException if an ordinate value in the minimum point is not
0139: * less than or equal to the corresponding ordinate value in the maximum point.
0140: */
0141: public GeneralEnvelope(final GeneralDirectPosition minDP,
0142: final GeneralDirectPosition maxDP)
0143: throws IllegalArgumentException {
0144: // Uncomment next lines if Sun fixes RFE #4093999
0145: // ensureNonNull("minDP", minDP);
0146: // ensureNonNull("maxDP", maxDP);
0147: this (minDP.ordinates, maxDP.ordinates);
0148: crs = getCoordinateReferenceSystem(minDP, maxDP);
0149: AbstractDirectPosition.checkCoordinateReferenceSystemDimension(
0150: crs, ordinates.length / 2);
0151: }
0152:
0153: /**
0154: * Constructs an empty envelope with the specified coordinate reference system.
0155: * All ordinates are initialized to 0.
0156: *
0157: * @since 2.2
0158: */
0159: public GeneralEnvelope(final CoordinateReferenceSystem crs) {
0160: // Uncomment next line if Sun fixes RFE #4093999
0161: // ensureNonNull("envelope", envelope);
0162: this (crs.getCoordinateSystem().getDimension());
0163: this .crs = crs;
0164: }
0165:
0166: /**
0167: * Constructs a new envelope with the same data than the specified envelope.
0168: */
0169: public GeneralEnvelope(final Envelope envelope) {
0170: ensureNonNull("envelope", envelope);
0171: if (envelope instanceof GeneralEnvelope) {
0172: final GeneralEnvelope e = (GeneralEnvelope) envelope;
0173: ordinates = (double[]) e.ordinates.clone();
0174: crs = e.crs;
0175: } else {
0176: crs = envelope.getCoordinateReferenceSystem();
0177: final int dimension = envelope.getDimension();
0178: ordinates = new double[2 * dimension];
0179: for (int i = 0; i < dimension; i++) {
0180: ordinates[i] = envelope.getMinimum(i);
0181: ordinates[i + dimension] = envelope.getMaximum(i);
0182: }
0183: checkCoherence();
0184: }
0185: }
0186:
0187: /**
0188: * Constructs a new envelope with the same data than the specified
0189: * geographic bounding box. The coordinate reference system is set
0190: * to {@linkplain DefaultGeographicCRS#WGS84 WGS84}.
0191: *
0192: * @since 2.4
0193: */
0194: public GeneralEnvelope(final GeographicBoundingBox box) {
0195: ensureNonNull("box", box);
0196: ordinates = new double[] { box.getWestBoundLongitude(),
0197: box.getSouthBoundLatitude(),
0198: box.getEastBoundLongitude(),
0199: box.getNorthBoundLatitude() };
0200: crs = DefaultGeographicCRS.WGS84;
0201: }
0202:
0203: /**
0204: * Constructs two-dimensional envelope defined by a {@link Rectangle2D}.
0205: * The coordinate reference system is initially undefined.
0206: */
0207: public GeneralEnvelope(final Rectangle2D rect) {
0208: ensureNonNull("rect", rect);
0209: ordinates = new double[] { rect.getMinX(), rect.getMinY(),
0210: rect.getMaxX(), rect.getMaxY() };
0211: checkCoherence();
0212: }
0213:
0214: /**
0215: * Creates an envelope for a grid range transformed using the specified math transform.
0216: *
0217: * @param gridRange The grid range.
0218: * @param gridType Whatever grid range coordinates map to pixel center or pixel corner.
0219: * @param gridToCRS The transform (usually affine) from grid range to the envelope CRS.
0220: * @param crs The envelope CRS, or {@code null} if unknow.
0221: *
0222: * @throws MismatchedDimensionException If one of the supplied object doesn't have
0223: * a dimension compatible with the other objects.
0224: * @throws IllegalArgumentException if an argument is illegal for some other reason,
0225: * including failure to use the provided math transform.
0226: *
0227: * @since 2.3
0228: */
0229: public GeneralEnvelope(final GridRange gridRange,
0230: final PixelInCell gridType, final MathTransform gridToCRS,
0231: final CoordinateReferenceSystem crs)
0232: throws IllegalArgumentException {
0233: ensureNonNull("gridRange", gridRange);
0234: ensureNonNull("gridToCRS", gridToCRS);
0235: final int dimRange = gridRange.getDimension();
0236: final int dimSource = gridToCRS.getSourceDimensions();
0237: final int dimTarget = gridToCRS.getTargetDimensions();
0238: ensureSameDimension(dimRange, dimSource);
0239: ensureSameDimension(dimRange, dimTarget);
0240: ordinates = new double[dimSource * 2];
0241: final double offset;
0242: if (PixelInCell.CELL_CENTER.equals(gridType)) {
0243: offset = 0.5;
0244: } else if (PixelInCell.CELL_CORNER.equals(gridType)) {
0245: offset = 0.0;
0246: } else {
0247: throw new IllegalArgumentException(Errors
0248: .format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "gridType",
0249: gridType));
0250: }
0251: for (int i = 0; i < dimSource; i++) {
0252: // According OpenGIS specification, GridGeometry maps pixel's center.
0253: // We want a bounding box for all pixels, not pixel's centers. Offset by
0254: // 0.5 (use -0.5 for maximum too, not +0.5, since maximum is exclusive).
0255: setRange(i, gridRange.getLower(i) - offset, gridRange
0256: .getUpper(i)
0257: - offset);
0258: }
0259: final GeneralEnvelope transformed;
0260: try {
0261: transformed = CRS.transform(gridToCRS, this );
0262: } catch (TransformException exception) {
0263: throw new IllegalArgumentException(Errors.format(
0264: ErrorKeys.BAD_TRANSFORM_$1, Utilities
0265: .getShortClassName(gridToCRS))/*, exception*/);
0266: // TODO: uncomment the exception cause when we will be allowed to target J2SE 1.5.
0267: }
0268: assert transformed.ordinates.length == this .ordinates.length;
0269: System.arraycopy(transformed.ordinates, 0, this .ordinates, 0,
0270: ordinates.length);
0271: setCoordinateReferenceSystem(crs);
0272: }
0273:
0274: /**
0275: * Makes sure an argument is non-null.
0276: *
0277: * @param name Argument name.
0278: * @param object User argument.
0279: * @throws InvalidParameterValueException if {@code object} is null.
0280: */
0281: private static void ensureNonNull(final String name,
0282: final Object object) throws IllegalArgumentException {
0283: if (object == null) {
0284: throw new IllegalArgumentException(Errors.format(
0285: ErrorKeys.NULL_ARGUMENT_$1, name));
0286: }
0287: }
0288:
0289: /**
0290: * Make sure the specified dimensions are identical.
0291: */
0292: private static void ensureSameDimension(final int dim1,
0293: final int dim2) throws MismatchedDimensionException {
0294: if (dim1 != dim2) {
0295: throw new MismatchedDimensionException(Errors.format(
0296: ErrorKeys.MISMATCHED_DIMENSION_$2,
0297: new Integer(dim1), new Integer(dim2)));
0298: }
0299: }
0300:
0301: /**
0302: * Checks if ordinate values in the minimum point are less than or
0303: * equal to the corresponding ordinate value in the maximum point.
0304: *
0305: * @throws IllegalArgumentException if an ordinate value in the minimum
0306: * point is not less than or equal to the corresponding ordinate
0307: * value in the maximum point.
0308: */
0309: private void checkCoherence() throws IllegalArgumentException {
0310: final int dimension = ordinates.length / 2;
0311: for (int i = 0; i < dimension; i++) {
0312: if (!(ordinates[i] <= ordinates[dimension + i])) { // Use '!' in order to catch 'NaN'.
0313: throw new IllegalArgumentException(Errors.format(
0314: ErrorKeys.ILLEGAL_ENVELOPE_ORDINATE_$1,
0315: new Integer(i)));
0316: }
0317: }
0318: }
0319:
0320: /**
0321: * Returns the coordinate reference system from an arbitrary envelope, or {@code null}
0322: * if unknown. This method performs some sanity checking for ensuring that the envelope
0323: * CRS is consistent.
0324: *
0325: * @deprecated Use {@link Envelope#getCoordinateReferenceSystem()} instead.
0326: *
0327: * @since 2.3
0328: */
0329: public static CoordinateReferenceSystem getCoordinateReferenceSystem(
0330: final Envelope envelope) {
0331: if (envelope == null) {
0332: return null;
0333: }
0334: if (envelope instanceof GeneralEnvelope) {
0335: return ((GeneralEnvelope) envelope)
0336: .getCoordinateReferenceSystem();
0337: }
0338: if (envelope instanceof Envelope2D) {
0339: return ((Envelope2D) envelope)
0340: .getCoordinateReferenceSystem();
0341: }
0342: final DirectPosition lower = envelope.getLowerCorner();
0343: final DirectPosition upper = envelope.getUpperCorner();
0344: if (lower.getDimension() == upper.getDimension()) {
0345: final CoordinateReferenceSystem crs = lower
0346: .getCoordinateReferenceSystem();
0347: if (Utilities.equals(crs, upper
0348: .getCoordinateReferenceSystem())) {
0349: return crs;
0350: }
0351: }
0352: throw new IllegalArgumentException(Errors
0353: .format(ErrorKeys.MALFORMED_ENVELOPE));
0354: }
0355:
0356: /**
0357: * Returns the coordinate reference system in which the coordinates are given.
0358: *
0359: * @return The coordinate reference system, or {@code null}.
0360: */
0361: public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
0362: assert crs == null
0363: || crs.getCoordinateSystem().getDimension() == getDimension();
0364: return crs;
0365: }
0366:
0367: /**
0368: * Set the coordinate reference system in which the coordinate are given.
0369: * Note: this method <strong>do not</strong> reproject the envelope.
0370: *
0371: * @param crs The new coordinate reference system, or {@code null}.
0372: * @throws MismatchedDimensionException if the specified CRS doesn't have the expected
0373: * number of dimensions.
0374: */
0375: public void setCoordinateReferenceSystem(
0376: final CoordinateReferenceSystem crs)
0377: throws MismatchedDimensionException {
0378: AbstractDirectPosition.checkCoordinateReferenceSystemDimension(
0379: crs, getDimension());
0380: this .crs = crs;
0381: }
0382:
0383: /**
0384: * Returns the number of dimensions.
0385: */
0386: public final int getDimension() {
0387: return ordinates.length / 2;
0388: }
0389:
0390: /**
0391: * A coordinate position consisting of all the {@linkplain #getMinimum minimal ordinates}
0392: * for each dimension for all points within the {@code Envelope}.
0393: *
0394: * @return The lower corner.
0395: */
0396: //@Override
0397: public DirectPosition getLowerCorner() {
0398: final int dim = ordinates.length / 2;
0399: final GeneralDirectPosition position = new GeneralDirectPosition(
0400: dim);
0401: System.arraycopy(ordinates, 0, position.ordinates, 0, dim);
0402: position.setCoordinateReferenceSystem(crs);
0403: return position;
0404: }
0405:
0406: /**
0407: * A coordinate position consisting of all the {@linkplain #getMaximum maximal ordinates}
0408: * for each dimension for all points within the {@code Envelope}.
0409: *
0410: * @return The upper corner.
0411: */
0412: //@Override
0413: public DirectPosition getUpperCorner() {
0414: final int dim = ordinates.length / 2;
0415: final GeneralDirectPosition position = new GeneralDirectPosition(
0416: dim);
0417: System.arraycopy(ordinates, dim, position.ordinates, 0, dim);
0418: position.setCoordinateReferenceSystem(crs);
0419: return position;
0420: }
0421:
0422: /**
0423: * A coordinate position consisting of all the {@linkplain #getCenter(int) middle ordinates}
0424: * for each dimension for all points within the {@code Envelope}.
0425: *
0426: * @since 2.3
0427: */
0428: public DirectPosition getCenter() {
0429: final GeneralDirectPosition position = new GeneralDirectPosition(
0430: ordinates.length / 2);
0431: for (int i = position.ordinates.length; --i >= 0;) {
0432: position.ordinates[i] = getCenter(i);
0433: }
0434: position.setCoordinateReferenceSystem(crs);
0435: return position;
0436: }
0437:
0438: /**
0439: * Returns the minimal ordinate along the specified dimension.
0440: */
0441: public final double getMinimum(final int dimension) {
0442: if (dimension < ordinates.length / 2) {
0443: return ordinates[dimension];
0444: } else {
0445: throw new ArrayIndexOutOfBoundsException(dimension);
0446: }
0447: }
0448:
0449: /**
0450: * Returns the maximal ordinate along the specified dimension.
0451: */
0452: public final double getMaximum(final int dimension) {
0453: if (dimension >= 0) {
0454: return ordinates[dimension + ordinates.length / 2];
0455: } else {
0456: throw new ArrayIndexOutOfBoundsException(dimension);
0457: }
0458: }
0459:
0460: /**
0461: * Returns the center ordinate along the specified dimension.
0462: */
0463: public final double getCenter(final int dimension) {
0464: return 0.5 * (ordinates[dimension] + ordinates[dimension
0465: + ordinates.length / 2]);
0466: }
0467:
0468: /**
0469: * Returns the envelope length along the specified dimension.
0470: * This length is equals to the maximum ordinate minus the
0471: * minimal ordinate.
0472: */
0473: public final double getLength(final int dimension) {
0474: return ordinates[dimension + ordinates.length / 2]
0475: - ordinates[dimension];
0476: }
0477:
0478: /**
0479: * Returns the envelope length along the specified dimension, in terms of the given units.
0480: *
0481: * @param unit The unit for the return value.
0482: * @return The length in terms of the given unit.
0483: * @throws ConversionException if the length can't be converted to the specified units.
0484: *
0485: * @since 2.2
0486: */
0487: public double getLength(final int dimension, final Unit unit)
0488: throws ConversionException {
0489: double value = getLength(dimension);
0490: if (crs != null) {
0491: final Unit source = crs.getCoordinateSystem().getAxis(
0492: dimension).getUnit();
0493: if (source != null) {
0494: value = source.getConverterTo(unit).convert(value);
0495: }
0496: }
0497: return value;
0498: }
0499:
0500: /**
0501: * Set the envelope's range along the specified dimension.
0502: *
0503: * @param dimension The dimension to set.
0504: * @param minimum The minimum value along the specified dimension.
0505: * @param maximum The maximum value along the specified dimension.
0506: */
0507: public void setRange(final int dimension, double minimum,
0508: double maximum) {
0509: if (minimum > maximum) {
0510: // Make an empty envelope (min==max)
0511: // while keeping it legal (min<=max).
0512: minimum = maximum = 0.5 * (minimum + maximum);
0513: }
0514: if (dimension >= 0) {
0515: // An exception will be thrown before any change if 'dimension' is out of range.
0516: ordinates[dimension + ordinates.length / 2] = maximum;
0517: ordinates[dimension] = minimum;
0518: } else {
0519: throw new ArrayIndexOutOfBoundsException(dimension);
0520: }
0521: }
0522:
0523: /**
0524: * Set this envelope to the same coordinate values than the specified envelope.
0525: *
0526: * @param envelope The new envelope to copy coordinates from.
0527: * @throws MismatchedDimensionException if the specified envelope doesn't have the expected
0528: * number of dimensions.
0529: *
0530: * @since 2.2
0531: */
0532: public void setEnvelope(final GeneralEnvelope envelope)
0533: throws MismatchedDimensionException {
0534: ensureNonNull("envelope", envelope);
0535: AbstractDirectPosition.ensureDimensionMatch("envelope",
0536: envelope.getDimension(), getDimension());
0537: System.arraycopy(envelope.ordinates, 0, ordinates, 0,
0538: ordinates.length);
0539: if (envelope.crs != null) {
0540: crs = envelope.crs;
0541: assert crs.getCoordinateSystem().getDimension() == getDimension() : crs;
0542: assert !envelope.getClass().equals(getClass())
0543: || equals(envelope) : envelope;
0544: }
0545: }
0546:
0547: /**
0548: * Sets the lower corner to {@linkplain Double#NEGATIVE_INFINITY negative infinity}
0549: * and the upper corner to {@linkplain Double#POSITIVE_INFINITY positive infinity}.
0550: * The {@linkplain #getCoordinateReferenceSystem coordinate reference system} (if any)
0551: * stay unchanged.
0552: *
0553: * @since 2.2
0554: */
0555: public void setToInfinite() {
0556: final int mid = ordinates.length / 2;
0557: Arrays.fill(ordinates, 0, mid, Double.NEGATIVE_INFINITY);
0558: Arrays.fill(ordinates, mid, ordinates.length,
0559: Double.POSITIVE_INFINITY);
0560: assert isInfinite() : this ;
0561: }
0562:
0563: /**
0564: * Returns {@code true} if at least one ordinate has an
0565: * {@linkplain Double#isInfinite infinite} value.
0566: *
0567: * @since 2.2
0568: */
0569: public boolean isInfinite() {
0570: for (int i = 0; i < ordinates.length; i++) {
0571: if (Double.isInfinite(ordinates[i])) {
0572: return true;
0573: }
0574: }
0575: return false;
0576: }
0577:
0578: /**
0579: * Sets all ordinate values to {@linkplain Double#NaN NaN}. The
0580: * {@linkplain #getCoordinateReferenceSystem coordinate reference system} (if any) stay
0581: * unchanged.
0582: *
0583: * @since 2.2
0584: */
0585: public void setToNull() {
0586: Arrays.fill(ordinates, Double.NaN);
0587: assert isNull() : this ;
0588: }
0589:
0590: /**
0591: * Returns {@code false} if at least one ordinate value is not {@linkplain Double#NaN NaN}. The
0592: * {@code isNull()} check is a little bit different than {@link #isEmpty()} since it returns
0593: * {@code false} for a partially initialized envelope, while {@code isEmpty()} returns
0594: * {@code false} only after all dimensions have been initialized. More specifically, the
0595: * following rules apply:
0596: * <p>
0597: * <ul>
0598: * <li>If <code>isNull() == true</code>, then <code>{@linkplain #isEmpty()} == true</code></li>
0599: * <li>If <code>{@linkplain #isEmpty()} == false</code>, then <code>isNull() == false</code></li>
0600: * <li>The converse of the above-cited rules are not always true.</li>
0601: * </ul>
0602: *
0603: * @since 2.2
0604: */
0605: public boolean isNull() {
0606: for (int i = 0; i < ordinates.length; i++) {
0607: if (!Double.isNaN(ordinates[i])) {
0608: return false;
0609: }
0610: }
0611: assert isEmpty() : this ;
0612: return true;
0613: }
0614:
0615: /**
0616: * Determines whether or not this envelope is empty. An envelope is non-empty only if it has
0617: * at least one {@linkplain #getDimension dimension}, and the {@linkplain #getLength length}
0618: * is greater than 0 along all dimensions. Note that a non-empty envelope is always
0619: * non-{@linkplain #isNull null}, but the converse is not always true.
0620: */
0621: public boolean isEmpty() {
0622: final int dimension = ordinates.length / 2;
0623: if (dimension == 0) {
0624: return true;
0625: }
0626: for (int i = 0; i < dimension; i++) {
0627: if (!(ordinates[i] < ordinates[i + dimension])) { // Use '!' in order to catch NaN
0628: return true;
0629: }
0630: }
0631: assert !isNull() : this ;
0632: return false;
0633: }
0634:
0635: /**
0636: * Returns {@code true} if at least one of the specified CRS is null, or both CRS are equals.
0637: * This special processing for {@code null} values is different from the usual contract of an
0638: * {@code equals} method, but allow to handle the case where the CRS is unknown.
0639: */
0640: private static boolean equalsIgnoreMetadata(
0641: final CoordinateReferenceSystem crs1,
0642: final CoordinateReferenceSystem crs2) {
0643: return crs1 == null || crs2 == null
0644: || CRS.equalsIgnoreMetadata(crs1, crs2);
0645: }
0646:
0647: /**
0648: * Adds a point to this envelope. The resulting envelope is the smallest envelope that
0649: * contains both the original envelope and the specified point. After adding a point,
0650: * a call to {@link #contains} with the added point as an argument will return {@code true},
0651: * except if one of the point's ordinates was {@link Double#NaN} (in which case the
0652: * corresponding ordinate have been ignored).
0653: * <p>
0654: * This method assumes that the specified point uses the same CRS than this envelope.
0655: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0656: *
0657: * @param position The point to add.
0658: * @throws MismatchedDimensionException if the specified point doesn't have
0659: * the expected dimension.
0660: */
0661: public void add(final DirectPosition position)
0662: throws MismatchedDimensionException {
0663: ensureNonNull("position", position);
0664: final int dim = ordinates.length / 2;
0665: AbstractDirectPosition.ensureDimensionMatch("position",
0666: position.getDimension(), dim);
0667: assert equalsIgnoreMetadata(crs, position
0668: .getCoordinateReferenceSystem()) : position;
0669: for (int i = 0; i < dim; i++) {
0670: final double value = position.getOrdinate(i);
0671: if (value < ordinates[i])
0672: ordinates[i] = value;
0673: if (value > ordinates[i + dim])
0674: ordinates[i + dim] = value;
0675: }
0676: assert isEmpty() || contains(position);
0677: }
0678:
0679: /**
0680: * Adds an envelope object to this envelope. The resulting envelope is the union of the
0681: * two {@code Envelope} objects.
0682: * <p>
0683: * This method assumes that the specified envelope uses the same CRS than this envelope.
0684: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0685: *
0686: * @param envelope the {@code Envelope} to add to this envelope.
0687: * @throws MismatchedDimensionException if the specified envelope doesn't
0688: * have the expected dimension.
0689: */
0690: public void add(final Envelope envelope)
0691: throws MismatchedDimensionException {
0692: ensureNonNull("envelope", envelope);
0693: final int dim = ordinates.length / 2;
0694: AbstractDirectPosition.ensureDimensionMatch("envelope",
0695: envelope.getDimension(), dim);
0696: assert equalsIgnoreMetadata(crs, envelope
0697: .getCoordinateReferenceSystem()) : envelope;
0698: for (int i = 0; i < dim; i++) {
0699: final double min = envelope.getMinimum(i);
0700: final double max = envelope.getMaximum(i);
0701: if (min < ordinates[i])
0702: ordinates[i] = min;
0703: if (max > ordinates[i + dim])
0704: ordinates[i + dim] = max;
0705: }
0706: assert isEmpty() || contains(envelope, true);
0707: }
0708:
0709: /**
0710: * Tests if a specified coordinate is inside the boundary of this envelope.
0711: * <p>
0712: * This method assumes that the specified point uses the same CRS than this envelope.
0713: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0714: *
0715: * @param position The point to text.
0716: * @return {@code true} if the specified coordinates are inside the boundary
0717: * of this envelope; {@code false} otherwise.
0718: * @throws MismatchedDimensionException if the specified point doesn't have
0719: * the expected dimension.
0720: */
0721: public boolean contains(final DirectPosition position)
0722: throws MismatchedDimensionException {
0723: ensureNonNull("position", position);
0724: final int dim = ordinates.length / 2;
0725: AbstractDirectPosition.ensureDimensionMatch("point", position
0726: .getDimension(), dim);
0727: assert equalsIgnoreMetadata(crs, position
0728: .getCoordinateReferenceSystem()) : position;
0729: for (int i = 0; i < dim; i++) {
0730: final double value = position.getOrdinate(i);
0731: if (!(value >= ordinates[i]))
0732: return false;
0733: if (!(value <= ordinates[i + dim]))
0734: return false;
0735: // Use '!' in order to take 'NaN' in account.
0736: }
0737: return true;
0738: }
0739:
0740: /**
0741: * Returns {@code true} if this envelope completly encloses the specified envelope.
0742: * If one or more edges from the specified envelope coincide with an edge from this
0743: * envelope, then this method returns {@code true} only if {@code edgesInclusive}
0744: * is {@code true}.
0745: * <p>
0746: * This method assumes that the specified envelope uses the same CRS than this envelope.
0747: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0748: *
0749: * @param envelope The envelope to test for inclusion.
0750: * @param edgesInclusive {@code true} if this envelope edges are inclusive.
0751: * @return {@code true} if this envelope completly encloses the specified one.
0752: * @throws MismatchedDimensionException if the specified envelope doesn't have
0753: * the expected dimension.
0754: *
0755: * @see #intersects(Envelope, boolean)
0756: * @see #equals(Envelope, double)
0757: *
0758: * @since 2.2
0759: */
0760: public boolean contains(final Envelope envelope,
0761: final boolean edgesInclusive)
0762: throws MismatchedDimensionException {
0763: ensureNonNull("envelope", envelope);
0764: final int dim = ordinates.length / 2;
0765: AbstractDirectPosition.ensureDimensionMatch("envelope",
0766: envelope.getDimension(), dim);
0767: assert equalsIgnoreMetadata(crs, envelope
0768: .getCoordinateReferenceSystem()) : envelope;
0769: for (int i = 0; i < dim; i++) {
0770: double inner = envelope.getMinimum(i);
0771: double outer = ordinates[i];
0772: if (!(edgesInclusive ? inner >= outer : inner > outer)) { // ! is for catching NaN.
0773: return false;
0774: }
0775: inner = envelope.getMaximum(i);
0776: outer = ordinates[i + dim];
0777: if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN.
0778: return false;
0779: }
0780: }
0781: assert intersects(envelope, edgesInclusive);
0782: return true;
0783: }
0784:
0785: /**
0786: * Returns {@code true} if this envelope intersects the specified envelope.
0787: * If one or more edges from the specified envelope coincide with an edge from this
0788: * envelope, then this method returns {@code true} only if {@code edgesInclusive}
0789: * is {@code true}.
0790: * <p>
0791: * This method assumes that the specified envelope uses the same CRS than this envelope.
0792: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0793: *
0794: * @param envelope The envelope to test for intersection.
0795: * @param edgesInclusive {@code true} if this envelope edges are inclusive.
0796: * @return {@code true} if this envelope intersects the specified one.
0797: * @throws MismatchedDimensionException if the specified envelope doesn't have
0798: * the expected dimension.
0799: *
0800: * @see #contains(Envelope, boolean)
0801: * @see #equals(Envelope, double)
0802: *
0803: * @since 2.2
0804: */
0805: public boolean intersects(final Envelope envelope,
0806: final boolean edgesInclusive)
0807: throws MismatchedDimensionException {
0808: ensureNonNull("envelope", envelope);
0809: final int dim = ordinates.length / 2;
0810: AbstractDirectPosition.ensureDimensionMatch("envelope",
0811: envelope.getDimension(), dim);
0812: assert equalsIgnoreMetadata(crs, envelope
0813: .getCoordinateReferenceSystem()) : envelope;
0814: for (int i = 0; i < dim; i++) {
0815: double inner = envelope.getMaximum(i);
0816: double outer = ordinates[i];
0817: if (!(edgesInclusive ? inner >= outer : inner > outer)) { // ! is for catching NaN.
0818: return false;
0819: }
0820: inner = envelope.getMinimum(i);
0821: outer = ordinates[i + dim];
0822: if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN.
0823: return false;
0824: }
0825: }
0826: return true;
0827: }
0828:
0829: /**
0830: * Sets this envelope to the intersection if this envelope with the specified one.
0831: * <p>
0832: * This method assumes that the specified envelope uses the same CRS than this envelope.
0833: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0834: *
0835: * @param envelope the {@code Envelope} to intersect to this envelope.
0836: * @throws MismatchedDimensionException if the specified envelope doesn't
0837: * have the expected dimension.
0838: */
0839: public void intersect(final Envelope envelope)
0840: throws MismatchedDimensionException {
0841: ensureNonNull("envelope", envelope);
0842: final int dim = ordinates.length / 2;
0843: AbstractDirectPosition.ensureDimensionMatch("envelope",
0844: envelope.getDimension(), dim);
0845: assert equalsIgnoreMetadata(crs, envelope
0846: .getCoordinateReferenceSystem()) : envelope;
0847: for (int i = 0; i < dim; i++) {
0848: double min = Math.max(ordinates[i], envelope.getMinimum(i));
0849: double max = Math.min(ordinates[i + dim], envelope
0850: .getMaximum(i));
0851: if (min > max) {
0852: // Make an empty envelope (min==max)
0853: // while keeping it legal (min<=max).
0854: min = max = 0.5 * (min + max);
0855: }
0856: ordinates[i] = min;
0857: ordinates[i + dim] = max;
0858: }
0859: }
0860:
0861: /**
0862: * Returns a new envelope that encompass only some dimensions of this envelope.
0863: * This method copy this envelope's ordinates into a new envelope, beginning at
0864: * dimension <code>lower</code> and extending to dimension <code>upper-1</code>.
0865: * Thus the dimension of the subenvelope is <code>upper-lower</code>.
0866: *
0867: * @param lower The first dimension to copy, inclusive.
0868: * @param upper The last dimension to copy, exclusive.
0869: * @return The subenvelope.
0870: * @throws IndexOutOfBoundsException if an index is out of bounds.
0871: */
0872: public GeneralEnvelope getSubEnvelope(final int lower,
0873: final int upper) throws IndexOutOfBoundsException {
0874: final int curDim = ordinates.length / 2;
0875: final int newDim = upper - lower;
0876: if (lower < 0 || lower > curDim) {
0877: throw new IndexOutOfBoundsException(Errors.format(
0878: ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower",
0879: new Integer(lower)));
0880: }
0881: if (newDim < 0 || upper > curDim) {
0882: throw new IndexOutOfBoundsException(Errors.format(
0883: ErrorKeys.ILLEGAL_ARGUMENT_$2, "upper",
0884: new Integer(upper)));
0885: }
0886: final GeneralEnvelope envelope = new GeneralEnvelope(newDim);
0887: System.arraycopy(ordinates, lower, envelope.ordinates, 0,
0888: newDim);
0889: System.arraycopy(ordinates, lower + curDim, envelope.ordinates,
0890: newDim, newDim);
0891: return envelope;
0892: }
0893:
0894: /**
0895: * Returns a new envelope with the same values than this envelope minus the
0896: * specified range of dimensions.
0897: *
0898: * @param lower The first dimension to omit, inclusive.
0899: * @param upper The last dimension to omit, exclusive.
0900: * @return The subenvelope.
0901: * @throws IndexOutOfBoundsException if an index is out of bounds.
0902: */
0903: public GeneralEnvelope getReducedEnvelope(final int lower,
0904: final int upper) throws IndexOutOfBoundsException {
0905: final int curDim = ordinates.length / 2;
0906: final int rmvDim = upper - lower;
0907: if (lower < 0 || lower > curDim) {
0908: throw new IndexOutOfBoundsException(Errors.format(
0909: ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower",
0910: new Integer(lower)));
0911: }
0912: if (rmvDim < 0 || upper > curDim) {
0913: throw new IndexOutOfBoundsException(Errors.format(
0914: ErrorKeys.ILLEGAL_ARGUMENT_$2, "upper",
0915: new Integer(upper)));
0916: }
0917: final GeneralEnvelope envelope = new GeneralEnvelope(curDim
0918: - rmvDim);
0919: System.arraycopy(ordinates, 0, envelope.ordinates, 0, lower);
0920: System.arraycopy(ordinates, lower, envelope.ordinates, upper,
0921: curDim - upper);
0922: return envelope;
0923: }
0924:
0925: /**
0926: * Returns a {@link Rectangle2D} with the same bounds as this {@code Envelope}.
0927: * This is a convenience method for interoperability with Java2D.
0928: *
0929: * @throws IllegalStateException if this envelope is not two-dimensional.
0930: */
0931: public Rectangle2D toRectangle2D() throws IllegalStateException {
0932: if (ordinates.length == 4) {
0933: return XRectangle2D.createFromExtremums(ordinates[0],
0934: ordinates[1], ordinates[2], ordinates[3]);
0935: } else {
0936: throw new IllegalStateException(Errors.format(
0937: ErrorKeys.NOT_TWO_DIMENSIONAL_$1, new Integer(
0938: getDimension())));
0939: }
0940: }
0941:
0942: /**
0943: * Returns a hash value for this envelope.
0944: */
0945: public int hashCode() {
0946: int code = GeneralDirectPosition.hashCode(ordinates);
0947: if (crs != null) {
0948: code += crs.hashCode();
0949: }
0950: assert code == super .hashCode();
0951: return code;
0952: }
0953:
0954: /**
0955: * Compares the specified object with this envelope for equality.
0956: */
0957: public boolean equals(final Object object) {
0958: if (object != null && object.getClass().equals(getClass())) {
0959: final GeneralEnvelope that = (GeneralEnvelope) object;
0960: return Arrays.equals(this .ordinates, that.ordinates)
0961: && Utilities.equals(this .crs, that.crs);
0962: }
0963: return false;
0964: }
0965:
0966: /**
0967: * Compares to the specified envelope for equality up to the specified relative tolerance value.
0968: * The tolerance value {@code eps} is relative to the {@linkplain #getLength envelope length}
0969: * along each dimension. More specifically, the actual tolerance value for a given dimension
0970: * <var>i</var> is {@code eps}×{@code length} where {@code length} is the maximum of
0971: * {@linkplain #getLength this envelope length} and the specified envelope length along
0972: * dimension <var>i</var>.
0973: * <p>
0974: * Relative tolerance value (as opposed to absolute tolerance value) help to workaround the
0975: * fact that tolerance value are CRS dependent. For example the tolerance value need to be
0976: * smaller for geographic CRS than for UTM projections, because the former typically has a
0977: * range of -180 to 180° while the later can have a range of thousands of meters.
0978: * <p>
0979: * This method assumes that the specified envelope uses the same CRS than this envelope.
0980: * For performance reason, it will no be verified unless J2SE assertions are enabled.
0981: *
0982: * @see #contains(Envelope, boolean)
0983: * @see #intersects(Envelope, boolean)
0984: *
0985: * @since 2.3
0986: *
0987: * @deprecated Use {@link #equals(Envelope, double, boolean)} instead.
0988: */
0989: public boolean equals(final Envelope envelope, final double eps) {
0990: return equals(envelope, eps, true);
0991: }
0992:
0993: /**
0994: * Compares to the specified envelope for equality up to the specified tolerance value.
0995: * The tolerance value {@code eps} can be either relative to the {@linkplain #getLength
0996: * envelope length} along each dimension or can be an absolute value (as for example some
0997: * ground resolution of a {@linkplain org.opengis.coverage.grid.GridCoverage grid coverage}).
0998: * <p>
0999: * If {@code relativeToLength} is set to {@code true}, the actual tolerance value for a given
1000: * dimension <var>i</var> is {@code eps}×{@code length} where {@code length} is the
1001: * maximum of {@linkplain #getLength this envelope length} and the specified envelope length
1002: * along dimension <var>i</var>.
1003: * <p>
1004: * If {@code relativeToLength} is set to {@code false}, the actual tolerance value for a
1005: * given dimension <var>i</var> is {@code eps}.
1006: * <p>
1007: * Relative tolerance value (as opposed to absolute tolerance value) help to workaround the
1008: * fact that tolerance value are CRS dependent. For example the tolerance value need to be
1009: * smaller for geographic CRS than for UTM projections, because the former typically has a
1010: * range of -180 to 180° while the later can have a range of thousands of meters.
1011: * <p>
1012: * This method assumes that the specified envelope uses the same CRS than this envelope.
1013: * For performance reason, it will no be verified unless J2SE assertions are enabled.
1014: *
1015: * @param envelope The envelope to compare with.
1016: * @param eps The tolerance value to use for numerical comparaisons.
1017: * @param relativeToLength {@code true} if the tolerance value should be relative to
1018: * axis length, or {@code false} if it is an absolute value.
1019: *
1020: * @see #contains(Envelope, boolean)
1021: * @see #intersects(Envelope, boolean)
1022: *
1023: * @since 2.4
1024: */
1025: public boolean equals(final Envelope envelope, final double eps,
1026: final boolean relativeToLength) {
1027: ensureNonNull("envelope", envelope);
1028: final int dimension = getDimension();
1029: if (envelope.getDimension() != dimension) {
1030: return false;
1031: }
1032: assert equalsIgnoreMetadata(crs, envelope
1033: .getCoordinateReferenceSystem()) : envelope;
1034: for (int i = 0; i < dimension; i++) {
1035: double epsilon;
1036: if (relativeToLength) {
1037: epsilon = Math.max(getLength(i), envelope.getLength(i));
1038: epsilon = (epsilon > 0 && epsilon < Double.POSITIVE_INFINITY) ? epsilon
1039: * eps
1040: : eps;
1041: } else {
1042: epsilon = eps;
1043: }
1044: // Comparaison below uses '!' in order to catch NaN values.
1045: if (!(Math.abs(getMinimum(i) - envelope.getMinimum(i)) <= epsilon && Math
1046: .abs(getMaximum(i) - envelope.getMaximum(i)) <= epsilon)) {
1047: return false;
1048: }
1049: }
1050: return true;
1051: }
1052:
1053: /**
1054: * Returns a deep copy of this envelope.
1055: */
1056: public Object clone() {
1057: try {
1058: GeneralEnvelope e = (GeneralEnvelope) super .clone();
1059: e.ordinates = (double[]) e.ordinates.clone();
1060: return e;
1061: } catch (CloneNotSupportedException exception) {
1062: // Should not happen, since we are cloneable.
1063: throw new AssertionError(exception);
1064: }
1065: }
1066: }
|