Package org.geotools.geometry

Source Code of org.geotools.geometry.GeneralEnvelope

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2004-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;

import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.Arrays;

import javax.measure.converter.ConversionException;
import javax.measure.unit.Unit;

import org.geotools.metadata.iso.spatial.PixelTranslation;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.resources.Classes;
import org.geotools.resources.geometry.XRectangle2D;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.geometry.MismatchedReferenceSystemException;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.RangeMeaning;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.Cloneable;

/**
* A minimum bounding box or rectangle. Regardless of dimension, an {@code Envelope} can be
* represented without ambiguity as two {@linkplain DirectPosition direct positions} (coordinate
* points). To encode an {@code Envelope}, it is sufficient to encode these two points.
* <p>
* This particular implementation of {@code Envelope} is said "General" because it uses coordinates
* of an arbitrary dimension.
* <p>
* <strong>Tip:</strong> The metadata package provides a
* {@link org.opengis.metadata.extent.GeographicBoundingBox}, which can be used as a kind of
* envelope with a coordinate reference system fixed to WGS 84 (EPSG:4326).
*
* @since 2.0
*
*
* @source $URL$
*         http://svn.osgeo.org/geotools/branches/2.6.x/modules/library/referencing/src/main/java
*         /org/geotools/geometry/GeneralEnvelope.java $
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Simone Giannecchini
*
* @see Envelope2D
* @see org.geotools.geometry.jts.ReferencedEnvelope
* @see org.opengis.metadata.extent.GeographicBoundingBox
*/
public class GeneralEnvelope extends AbstractEnvelope implements Cloneable, Serializable {
    /**
     * Serial number for interoperability with different versions.
     */
    private static final long serialVersionUID = 1752330560227688940L;

    /**
     * Minimum and maximum ordinate values. The first half contains minimum ordinates, while the
     * last half contains maximum ordinates. This layout is convenient for the creation of lower and
     * upper corner direct positions.
     * <p>
     * Consider this reference as final; it is modified by {@link #clone} only.
     */
    private double[] ordinates;
   
    /**
     * The coordinate reference system, or {@code null}.
     */
    private CoordinateReferenceSystem crs;

    /**
     * Constructs an empty envelope of the specified dimension. All ordinates are initialized to 0
     * and the coordinate reference system is undefined.
     *
     * @param dimension
     *            The envelope dimension.
     */
    public GeneralEnvelope(final int dimension) {
        ordinates = new double[dimension * 2];
        for (int i = 0; i < dimension*2; i++) {
          ordinates[i] = Double.NaN;
        }
    }

    /**
     * Constructs one-dimensional envelope defined by a range of values.
     *
     * @param min
     *            The minimal value.
     * @param max
     *            The maximal value.
     */
    public GeneralEnvelope(final double min, final double max) {
        ordinates = new double[] { min, max };
        checkCoordinates(ordinates);
    }

    /**
     * Constructs a envelope defined by two positions.
     *
     * @param minDP
     *            Minimum ordinate values.
     * @param maxDP
     *            Maximum ordinate values.
     * @throws MismatchedDimensionException
     *             if the two positions don't have the same dimension.
     * @throws IllegalArgumentException
     *             if an ordinate value in the minimum point is not less than or equal to the
     *             corresponding ordinate value in the maximum point.
     */
    public GeneralEnvelope(final double[] minDP, final double[] maxDP)
            throws IllegalArgumentException {
        ensureNonNull("minDP", minDP);
        ensureNonNull("maxDP", maxDP);
        ensureSameDimension(minDP.length, maxDP.length);
        ordinates = new double[minDP.length + maxDP.length];
        System.arraycopy(minDP, 0, ordinates, 0, minDP.length);
        System.arraycopy(maxDP, 0, ordinates, minDP.length, maxDP.length);
        checkCoordinates(ordinates);
    }

    /**
     * Constructs a envelope defined by two positions. The coordinate reference system is inferred
     * from the supplied direct position.
     *
     * @param minDP
     *            Point containing minimum ordinate values.
     * @param maxDP
     *            Point containing maximum ordinate values.
     * @throws MismatchedDimensionException
     *             if the two positions don't have the same dimension.
     * @throws MismatchedReferenceSystemException
     *             if the two positions don't use the same CRS.
     * @throws IllegalArgumentException
     *             if an ordinate value in the minimum point is not less than or equal to the
     *             corresponding ordinate value in the maximum point.
     */
    public GeneralEnvelope(final GeneralDirectPosition minDP, final GeneralDirectPosition maxDP)
            throws MismatchedReferenceSystemException, IllegalArgumentException {
        // Uncomment next lines if Sun fixes RFE #4093999
        // ensureNonNull("minDP", minDP);
        // ensureNonNull("maxDP", maxDP);
        this(minDP.ordinates, maxDP.ordinates);
        crs = getCoordinateReferenceSystem(minDP, maxDP);
        AbstractDirectPosition.checkCoordinateReferenceSystemDimension(crs, ordinates.length / 2);
    }

    /**
     * Constructs an empty envelope with the specified coordinate reference system. All ordinates
     * are initialized to 0.
     *
     * @param crs
     *            The coordinate reference system.
     *
     * @since 2.2
     */
    public GeneralEnvelope(final CoordinateReferenceSystem crs) {
        // Uncomment next line if Sun fixes RFE #4093999
        // ensureNonNull("envelope", envelope);
        this(crs.getCoordinateSystem().getDimension());
        this.crs = crs;
    }

    /**
     * Converts the envelope to a general envelope, avoiding the construction of a new object in
     * case the input envelope is already a GeneralEnvelope
     *
     * @param envelope
     * @return
     */
    public static GeneralEnvelope toGeneralEnvelope(final Envelope envelope) {
        if (envelope instanceof GeneralEnvelope) {
            return (GeneralEnvelope) envelope;
        } else {
            return new GeneralEnvelope(envelope);
        }
    }

    /**
     * Constructs a new envelope with the same data than the specified envelope.
     *
     * @param envelope
     *            The envelope to copy.
     */
    public GeneralEnvelope(final Envelope envelope) {
        ensureNonNull("envelope", envelope);
        if (envelope instanceof GeneralEnvelope) {
            final GeneralEnvelope e = (GeneralEnvelope) envelope;
            ordinates = e.ordinates.clone();
            crs = e.crs;
        } else {
            crs = envelope.getCoordinateReferenceSystem();
            final int dimension = envelope.getDimension();
            ordinates = new double[2 * dimension];
            for (int i = 0; i < dimension; i++) {
                ordinates[i] = envelope.getMinimum(i);
                ordinates[i + dimension] = envelope.getMaximum(i);
            }
            checkCoordinates(ordinates);
        }
    }

    /**
     * Constructs a new envelope with the same data than the specified geographic bounding box. The
     * coordinate reference system is set to {@linkplain DefaultGeographicCRS#WGS84 WGS84}.
     *
     * @param box
     *            The bounding box to copy.
     *
     * @since 2.4
     */
    public GeneralEnvelope(final GeographicBoundingBox box) {
        ensureNonNull("box", box);
        ordinates = new double[] { box.getWestBoundLongitude(), box.getSouthBoundLatitude(),
                box.getEastBoundLongitude(), box.getNorthBoundLatitude() };
        crs = DefaultGeographicCRS.WGS84;
    }

    /**
     * Constructs two-dimensional envelope defined by a {@link Rectangle2D}. The coordinate
     * reference system is initially undefined.
     *
     * @param rect
     *            The rectangle to copy.
     */
    public GeneralEnvelope(final Rectangle2D rect) {
        ensureNonNull("rect", rect);
        ordinates = new double[] { rect.getMinX(), rect.getMinY(), rect.getMaxX(), rect.getMaxY() };
        checkCoordinates(ordinates);
    }

    /**
     * Creates an envelope for a grid range transformed to an envelope using the specified math
     * transform. The <cite>grid to CRS</cite> transform should map either the
     * {@linkplain PixelInCell#CELL_CENTER cell center} (as in OGC convention) or
     * {@linkplain PixelInCell#CELL_CORNER cell corner} (as in Java2D/JAI convention) depending on
     * the {@code anchor} value. This constructor creates an envelope containing entirely all pixels
     * on a <cite>best effort</cite> basis - usually accurate for affine transforms.
     * <p>
     * <b>Note:</b> The convention is specified as a {@link PixelInCell} code instead than the more
     * detailled {@link org.opengis.metadata.spatial.PixelOrientation}, because the later is
     * restricted to the two-dimensional case while the former can be used for any number of
     * dimensions.
     *
     * @param gridRange
     *            The grid range.
     * @param anchor
     *            Whatever grid range coordinates map to pixel center or pixel corner.
     * @param gridToCRS
     *            The transform (usually affine) from grid range to the envelope CRS.
     * @param crs
     *            The envelope CRS, or {@code null} if unknow.
     *
     * @throws MismatchedDimensionException
     *             If one of the supplied object doesn't have a dimension compatible with the other
     *             objects.
     * @throws IllegalArgumentException
     *             if an argument is illegal for some other reason, including failure to use the
     *             provided math transform.
     *
     * @since 2.3
     *
     * @see org.geotools.coverage.grid.GeneralGridEnvelope#GeneralGridEnvelope(Envelope,PixelInCell,boolean)
     */
    public GeneralEnvelope(final GridEnvelope gridRange, final PixelInCell anchor,
            final MathTransform gridToCRS, final CoordinateReferenceSystem crs)
            throws IllegalArgumentException {
        ensureNonNull("gridRange", gridRange);
        ensureNonNull("gridToCRS", gridToCRS);
        final int dimRange = gridRange.getDimension();
        final int dimSource = gridToCRS.getSourceDimensions();
        final int dimTarget = gridToCRS.getTargetDimensions();
        ensureSameDimension(dimRange, dimSource);
        ensureSameDimension(dimRange, dimTarget);
        ordinates = new double[dimSource * 2];
        final double offset = PixelTranslation.getPixelTranslation(anchor) + 0.5;
        for (int i = 0; i < dimSource; i++) {
            /*
             * According OpenGIS specification, GridGeometry maps pixel's center. We want a bounding
             * box for all pixels, not pixel's centers. Offset by 0.5 (use -0.5 for maximum too, not
             * +0.5, since maximum is exclusive).
             *
             * Note: the offset of 1 after getHigh(i) is because high values are inclusive according
             * ISO specification, while our algorithm and Java usage expect exclusive values.
             */
            setRange(i, gridRange.getLow(i) - offset, gridRange.getHigh(i) - (offset - 1));
        }
        final GeneralEnvelope transformed;
        try {
            transformed = CRS.transform(gridToCRS, this);
        } catch (TransformException exception) {
            throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_TRANSFORM_$1, Classes
                    .getClass(gridToCRS)), exception);
        }
        assert transformed.ordinates.length == this.ordinates.length;
        System.arraycopy(transformed.ordinates, 0, this.ordinates, 0, ordinates.length);
        setCoordinateReferenceSystem(crs);
    }

    /**
     * Makes sure an argument is non-null.
     *
     * @param name
     *            Argument name.
     * @param object
     *            User argument.
     * @throws InvalidParameterValueException
     *             if {@code object} is null.
     */
    private static void ensureNonNull(final String name, final Object object)
            throws IllegalArgumentException {
        if (object == null) {
            throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, name));
        }
    }

    /**
     * Makes sure the specified dimensions are identical.
     */
    private static void ensureSameDimension(final int dim1, final int dim2)
            throws MismatchedDimensionException {
        if (dim1 != dim2) {
            throw new MismatchedDimensionException(Errors.format(ErrorKeys.MISMATCHED_DIMENSION_$2,
                    dim1, dim2));
        }
    }

    /**
     * Checks if ordinate values in the minimum point are less than or equal to the corresponding
     * ordinate value in the maximum point.
     * <p>
     * This code will recognize the following exceptions:
     * <ul>
     * <li>ordinates encoding isNil</li>
     * <li>ordinates encoding isEmpty</li>
     * </ul>
     * @throws IllegalArgumentException
     *             if an ordinate value in the minimum point is not less than or equal to the
     *             corresponding ordinate value in the maximum point.
     */
    private static void checkCoordinates(final double[] ordinates) throws IllegalArgumentException {
        if( isNilCoordinates( ordinates )){
            return; // null ordinates are okay           
        }
        if( isEmptyOrdinates(ordinates)){
            return; // empty ordinates are also a valid encoding....
        }
        final int dimension = ordinates.length / 2;
        for (int i = 0; i < dimension; i++) {
            if (!(ordinates[i] <= ordinates[dimension + i])) { // Use '!' in order to catch 'NaN'.
                throw new IllegalArgumentException(Errors.format(
                        ErrorKeys.ILLEGAL_ENVELOPE_ORDINATE_$1, i));
            }
        }
    }

    /**
     * Returns the coordinate reference system in which the coordinates are given.
     *
     * @return The coordinate reference system, or {@code null}.
     */
    public final CoordinateReferenceSystem getCoordinateReferenceSystem() {
        assert crs == null || crs.getCoordinateSystem().getDimension() == getDimension();
        return crs;
    }

    /**
     * Sets the coordinate reference system in which the coordinate are given. This method
     * <strong>do not</strong> reproject the envelope, and do not check if the envelope is contained
     * in the new domain of validity. The later can be enforced by a call to {@link #normalize}.
     *
     * @param crs
     *            The new coordinate reference system, or {@code null}.
     * @throws MismatchedDimensionException
     *             if the specified CRS doesn't have the expected number of dimensions.
     */
    public void setCoordinateReferenceSystem(final CoordinateReferenceSystem crs)
            throws MismatchedDimensionException {
        AbstractDirectPosition.checkCoordinateReferenceSystemDimension(crs, getDimension());
        this.crs = crs;
    }

    /**
     * Restricts this envelope to the CS or CRS
     * {@linkplain CoordinateReferenceSystem#getDomainOfValidity domain of validity}. This method
     * performs two steps:
     *
     * <ol>
     * <li>
     * <p>
     * First, it ensures that the envelope is contained in the {@linkplain CoordinateSystem
     * coordinate system} domain. Out of range ordinates are validated in a way that depends on the
     * {@linkplain CoordinateSystemAxis#getRangeMeaning range meaning}:
     * <ul>
     * <li>If {@linkplain RangeMeaning#EXACT EXACT} (typically <em>latitudes</em> ordinates), values
     * greater than the {@linkplain CoordinateSystemAxis#getMaximumValue maximum value} are replaced
     * by the maximum, and values smaller than the {@linkplain CoordinateSystemAxis#getMinimumValue
     * minimum value} are replaced by the minimum.</li>
     *
     * <li>If {@linkplain RangeMeaning#WRAPAROUND WRAPAROUND} (typically <em>longitudes</em>
     * ordinates), a multiple of the range (e.g. 360° for longitudes) is added or subtracted. If a
     * value stay out of range after this correction, then the ordinates are set to the full [
     * {@linkplain CoordinateSystemAxis#getMinimumValue minimum} ...
     * {@linkplain CoordinateSystemAxis#getMaximumValue maximum}] range.
     *
     * <blockquote> <b>Example:</b> [185° ... 190°] of longitude is equivalent to [-175° ...
     * -170°]. But [175° ... 185°] would be equivalent to [175° ... -175°], which is likely to
     * mislead most users of {@link Envelope} since the lower bounds is numerically greater than the
     * upper bounds. Reordering as [-175° ... 175°] would interchange the meaning of what is
     * "inside" and "outside" the envelope. So this implementation conservatively expands the range
     * to [-180° ... 180°] in order to ensure that the validated envelope fully contains the
     * original envelope. </blockquote></li>
     * </ul>
     * </p>
     * </li>
     * <li>
     * <p>
     * Second and only if {@code crsDomain} is {@code true}, the envelope normalized in the previous
     * step is intersected with the CRS {@linkplain CoordinateReferenceSystem#getDomainOfValidity
     * domain of validity}, if any.
     * </p>
     * </li>
     * </ol>
     *
     * @param crsDomain
     *            {@code true} if the envelope should be restricted to the CRS domain in addition of
     *            the CS domain.
     * @return {@code true} if this envelope has been modified, or {@code false} if no change was
     *         done.
     *
     * @since 2.5
     */
    public boolean normalize(final boolean crsDomain) {
        boolean changed = false;
        if (crs != null) {
            final int dimension = ordinates.length / 2;
            final CoordinateSystem cs = crs.getCoordinateSystem();
            for (int i = 0; i < dimension; i++) {
                final int j = i + dimension;
                final CoordinateSystemAxis axis = cs.getAxis(i);
                final double minimum = axis.getMinimumValue();
                final double maximum = axis.getMaximumValue();
                final RangeMeaning rm = axis.getRangeMeaning();
                if (RangeMeaning.EXACT.equals(rm)) {
                    if (ordinates[i] < minimum) {
                        ordinates[i] = minimum;
                        changed = true;
                    }
                    if (ordinates[j] > maximum) {
                        ordinates[j] = maximum;
                        changed = true;
                    }
                } else if (RangeMeaning.WRAPAROUND.equals(rm)) {
                    final double length = maximum - minimum;
                    if (length > 0 && length < Double.POSITIVE_INFINITY) {
                        final double offset = Math.floor((ordinates[i] - minimum) / length)
                                * length;
                        if (offset != 0) {
                            ordinates[i] -= offset;
                            ordinates[j] -= offset;
                            changed = true;
                        }
                        if (ordinates[j] > maximum) {
                            ordinates[i] = minimum; // See method Javadoc
                            ordinates[j] = maximum;
                            changed = true;
                        }
                    }
                }
            }
            if (crsDomain) {
                final Envelope domain = CRS.getEnvelope(crs);
                if (domain != null) {
                    final CoordinateReferenceSystem domainCRS = domain
                            .getCoordinateReferenceSystem();
                    if (domainCRS == null) {
                        intersect(domain);
                    } else {
                        /*
                         * The domain may have fewer dimensions than this envelope (typically only
                         * the ones relative to horizontal dimensions). We can rely on directions
                         * for matching axis since CRS.getEnvelope(crs) should have transformed the
                         * domain to this envelope CRS.
                         */
                        final CoordinateSystem domainCS = domainCRS.getCoordinateSystem();
                        final int domainDimension = domainCS.getDimension();
                        for (int i = 0; i < domainDimension; i++) {
                            final double minimum = domain.getMinimum(i);
                            final double maximum = domain.getMaximum(i);
                            final AxisDirection direction = domainCS.getAxis(i).getDirection();
                            for (int j = 0; j < dimension; j++) {
                                if (direction.equals(cs.getAxis(j).getDirection())) {
                                    final int k = j + dimension;
                                    if (ordinates[j] < minimum)
                                        ordinates[j] = minimum;
                                    if (ordinates[k] > maximum)
                                        ordinates[k] = maximum;
                                }
                            }
                        }
                    }
                }
            }
        }
        return changed;
    }

    /**
     * Returns the number of dimensions.
     */
    public final int getDimension() {
        return ordinates.length / 2;
    }

    /**
     * A coordinate position consisting of all the {@linkplain #getMinimum minimal ordinates} for
     * each dimension for all points within the {@code Envelope}.
     *
     * @return The lower corner.
     */
    @Override
    public DirectPosition getLowerCorner() {
        final int dim = ordinates.length / 2;
        final GeneralDirectPosition position = new GeneralDirectPosition(dim);
        System.arraycopy(ordinates, 0, position.ordinates, 0, dim);
        position.setCoordinateReferenceSystem(crs);
        return position;
    }

    /**
     * A coordinate position consisting of all the {@linkplain #getMaximum maximal ordinates} for
     * each dimension for all points within the {@code Envelope}.
     *
     * @return The upper corner.
     */
    @Override
    public DirectPosition getUpperCorner() {
        final int dim = ordinates.length / 2;
        final GeneralDirectPosition position = new GeneralDirectPosition(dim);
        System.arraycopy(ordinates, dim, position.ordinates, 0, dim);
        position.setCoordinateReferenceSystem(crs);
        return position;
    }

    /**
     * A coordinate position consisting of all the {@linkplain #getCenter(int) middle ordinates} for
     * each dimension for all points within the {@code Envelope}.
     *
     * @return The median coordinates.
     *
     * @since 2.5
     */
    public DirectPosition getMedian() {
        final GeneralDirectPosition position = new GeneralDirectPosition(ordinates.length / 2);
        for (int i = position.ordinates.length; --i >= 0;) {
            position.ordinates[i] = getMedian(i);
        }
        position.setCoordinateReferenceSystem(crs);
        return position;
    }

    /**
     * Creates an exception for an index out of bounds.
     */
    private static IndexOutOfBoundsException indexOutOfBounds(final int dimension) {
        return new IndexOutOfBoundsException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1,
                dimension));
    }

    /**
     * Returns the minimal ordinate along the specified dimension.
     *
     * @param dimension
     *            The dimension to query.
     * @return The minimal ordinate value along the given dimension.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     */
    public final double getMinimum(final int dimension) throws IndexOutOfBoundsException {
        if (dimension < ordinates.length / 2) {
            return ordinates[dimension];
        } else {
            throw indexOutOfBounds(dimension);
        }
    }

    /**
     * Returns the maximal ordinate along the specified dimension.
     *
     * @param dimension
     *            The dimension to query.
     * @return The maximal ordinate value along the given dimension.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     */
    public final double getMaximum(final int dimension) throws IndexOutOfBoundsException {
        if (dimension >= 0) {
            return ordinates[dimension + ordinates.length / 2];
        } else {
            throw indexOutOfBounds(dimension);
        }
    }

    /**
     * Returns the median ordinate along the specified dimension. The result should be equals (minus
     * rounding error) to <code>({@linkplain #getMaximum getMaximum}(dimension) -
     * {@linkplain #getMinimum getMinimum}(dimension)) / 2</code>.
     *
     * @param dimension
     *            The dimension to query.
     * @return The mid ordinate value along the given dimension.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     */
    public final double getMedian(final int dimension) throws IndexOutOfBoundsException {
        return 0.5 * (ordinates[dimension] + ordinates[dimension + ordinates.length / 2]);
    }

    /**
     * Returns the envelope span (typically width or height) along the specified dimension. The
     * result should be equals (minus rounding error) to <code>{@linkplain #getMaximum
     * getMaximum}(dimension) - {@linkplain #getMinimum getMinimum}(dimension)</code>.
     *
     * @param dimension
     *            The dimension to query.
     * @return The difference along maximal and minimal ordinates in the given dimension.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     */
    public final double getSpan(final int dimension) throws IndexOutOfBoundsException {
        return ordinates[dimension + ordinates.length / 2] - ordinates[dimension];
    }

    /**
     * Returns the envelope span along the specified dimension, in terms of the given units.
     *
     * @param dimension
     *            The dimension to query.
     * @param unit
     *            The unit for the return value.
     * @return The span in terms of the given unit.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     * @throws ConversionException
     *             if the length can't be converted to the specified units.
     *
     * @since 2.5
     */
    public double getSpan(final int dimension, final Unit<?> unit)
            throws IndexOutOfBoundsException, ConversionException {
        double value = getSpan(dimension);
        if (crs != null) {
            final Unit<?> source = crs.getCoordinateSystem().getAxis(dimension).getUnit();
            if (source != null) {
                value = source.getConverterTo(unit).convert(value);
            }
        }
        return value;
    }

    /**
     * Sets the envelope's range along the specified dimension.
     *
     * @param dimension
     *            The dimension to set.
     * @param minimum
     *            The minimum value along the specified dimension.
     * @param maximum
     *            The maximum value along the specified dimension.
     * @throws IndexOutOfBoundsException
     *             If the given index is out of bounds.
     */
    public void setRange(final int dimension, double minimum, double maximum)
            throws IndexOutOfBoundsException {
        if (minimum > maximum) {
            // Make an empty envelope (min == max)
            // while keeping it legal (min <= max).
            minimum = maximum = 0.5 * (minimum + maximum);
        }
        if (dimension >= 0) {
            // An exception will be thrown before any change if 'dimension' is out of range.
            ordinates[dimension + ordinates.length / 2] = maximum;
            ordinates[dimension] = minimum;
        } else {
            throw indexOutOfBounds(dimension);
        }
    }

    /**
     * Sets the envelope to the specified values, which must be the lower corner coordinates
     * followed by upper corner coordinates. The number of arguments provided shall be twice this
     * {@linkplain #getDimension envelope dimension}, and minimum shall not be greater than maximum.
     * <p>
     * <b>Example:</b> (<var>x</var><sub>min</sub>, <var>y</var><sub>min</sub>,
     * <var>z</var><sub>min</sub>, <var>x</var><sub>max</sub>, <var>y</var><sub>max</sub>,
     * <var>z</var><sub>max</sub>)
     *
     * @param ordinates
     *            The new ordinate values.
     *
     * @since 2.5
     */
    public void setEnvelope(final double... ordinates) {
        if ((ordinates.length & 1) != 0) {
            throw new IllegalArgumentException(Errors.format(ErrorKeys.ODD_ARRAY_LENGTH_$1,
                    ordinates.length));
        }
        final int dimension = ordinates.length >>> 1;
        final int check = this.ordinates.length >>> 1;
        if (dimension != check) {
            throw new MismatchedDimensionException(Errors.format(ErrorKeys.MISMATCHED_DIMENSION_$3,
                    "ordinates", dimension, check));
        }
        checkCoordinates(ordinates);
        System.arraycopy(ordinates, 0, this.ordinates, 0, ordinates.length);
    }

    /**
     * Sets this envelope to the same coordinate values than the specified envelope.
     *
     * @param envelope
     *            The new envelope to copy coordinates from.
     * @throws MismatchedDimensionException
     *             if the specified envelope doesn't have the expected number of dimensions.
     *
     * @since 2.2
     */
    public void setEnvelope(final GeneralEnvelope envelope) throws MismatchedDimensionException {
        ensureNonNull("envelope", envelope);
        AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(),
                getDimension());
        System.arraycopy(envelope.ordinates, 0, ordinates, 0, ordinates.length);
        if (envelope.crs != null) {
            crs = envelope.crs;
            assert crs.getCoordinateSystem().getDimension() == getDimension() : crs;
            assert !envelope.getClass().equals(getClass()) || equals(envelope) : envelope;
        }
    }

    /**
     * Sets the lower corner to {@linkplain Double#NEGATIVE_INFINITY negative infinity} and the
     * upper corner to {@linkplain Double#POSITIVE_INFINITY positive infinity}. The
     * {@linkplain #getCoordinateReferenceSystem coordinate reference system} (if any) stay
     * unchanged.
     *
     * @since 2.2
     */
    public void setToInfinite() {
        final int mid = ordinates.length / 2;
        Arrays.fill(ordinates, 0, mid, Double.NEGATIVE_INFINITY);
        Arrays.fill(ordinates, mid, ordinates.length, Double.POSITIVE_INFINITY);
        assert isInfinite() : this;
    }

    /**
     * Returns {@code true} if at least one ordinate has an {@linkplain Double#isInfinite infinite}
     * value.
     *
     * @return {@code true} if this envelope has infinite value.
     *
     * @since 2.2
     */
    public boolean isInfinite() {
        for (int i = 0; i < ordinates.length; i++) {
            if (Double.isInfinite(ordinates[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Sets all ordinate values to {@linkplain Double#NaN NaN}. The
     * {@linkplain #getCoordinateReferenceSystem coordinate reference system} (if any) stay
     * unchanged.
     *
     * @since 2.2
     */
    public void setToNull() {
        Arrays.fill(ordinates, Double.NaN);
        assert isNull() : this;
    }

    /**
     * Returns {@code false} if at least one ordinate value is not {@linkplain Double#NaN NaN}. The
     * {@code isNull()} check is a little bit different than {@link #isEmpty()} since it returns
     * {@code false} for a partially initialized envelope, while {@code isEmpty()} returns {@code
     * false} only after all dimensions have been initialized. More specifically, the following
     * rules apply:
     * <p>
     * <ul>
     * <li>If <code>isNull() == true</code>, then <code>{@linkplain #isEmpty()} == true</code></li>
     * <li>If <code>{@linkplain #isEmpty()} == false</code>, then <code>isNull() == false</code></li>
     * <li>The converse of the above-cited rules are not always true.</li>
     * </ul>
     *
     * @return {@code true} if this envelope has NaN values.
     *
     * @since 2.2
     */
    public boolean isNull() {
        if (!isNilCoordinates(ordinates)) {
            return false;
        }
        assert isEmpty() : this;
        return true;
    }

    /**
     * Check if the ordinates indicate a "nil" envelope.
     * @param ordinates
     * @return
     * @throws IllegalArgumentException
     */
    private static boolean isNilCoordinates(final double[] ordinates)
            throws IllegalArgumentException {
        for (int i = 0; i < ordinates.length; i++) {
            if (!Double.isNaN(ordinates[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Determines whether or not this envelope is empty. An envelope is non-empty only if it has at
     * least one {@linkplain #getDimension dimension}, and the {@linkplain #getLength length} is
     * greater than 0 along all dimensions. Note that a non-empty envelope is always non-
     * {@linkplain #isNull null}, but the converse is not always true.
     *
     * @return {@code true} if this envelope is empty.
     */
    public boolean isEmpty() {
        if( isEmptyOrdinates(ordinates)){
            return true;
        }
        assert !isNull() : this; // JG I worry that this is circular
        return false;
    }
    /**
     * Static method used to recognize an empty encoding of ordinates
     * @param ordinates
     * @return true of the ordinates indicate an empty envelope
     * @see #isEmpty()
     */
    private static boolean isEmptyOrdinates( double ordinates[] ){
        final int dimension = ordinates.length / 2;
        if (dimension == 0) {
            return true;
        }
        for (int i = 0; i < dimension; i++) {
            if (!(ordinates[i] < ordinates[i + dimension])) { // Use '!' in order to catch NaN
                return true;
            }
        }
        return false;
    }
    /**
     * Returns {@code true} if at least one of the specified CRS is null, or both CRS are equals.
     * This special processing for {@code null} values is different from the usual contract of an
     * {@code equals} method, but allow to handle the case where the CRS is unknown.
     */
    private static boolean equalsIgnoreMetadata(final CoordinateReferenceSystem crs1,
            final CoordinateReferenceSystem crs2) {
        return crs1 == null || crs2 == null || CRS.equalsIgnoreMetadata(crs1, crs2);
    }

    /**
     * Adds a point to this envelope. The resulting envelope is the smallest envelope that contains
     * both the original envelope and the specified point. After adding a point, a call to
     * {@link #contains} with the added point as an argument will return {@code true}, except if one
     * of the point's ordinates was {@link Double#NaN} (in which case the corresponding ordinate
     * have been ignored).
     * <p>
     * This method assumes that the specified point uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param position
     *            The point to add.
     * @throws MismatchedDimensionException
     *             if the specified point doesn't have the expected dimension.
     */
    public void add(final DirectPosition position) throws MismatchedDimensionException {
        ensureNonNull("position", position);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("position", position.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, position.getCoordinateReferenceSystem()) : position;
        for (int i = 0; i < dim; i++) {
            final double value = position.getOrdinate(i);
            if (value < ordinates[i] || Double.isNaN(ordinates[i]))
                ordinates[i] = value;
            if (value > ordinates[i + dim] || Double.isNaN(ordinates[i + dim]))
                ordinates[i + dim] = value;
        }
        assert isEmpty() || contains(position);
    }

    /**
     * Adds an envelope object to this envelope. The resulting envelope is the union of the two
     * {@code Envelope} objects.
     * <p>
     * This method assumes that the specified envelope uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param envelope
     *            the {@code Envelope} to add to this envelope.
     * @throws MismatchedDimensionException
     *             if the specified envelope doesn't have the expected dimension.
     */
    public void add(final Envelope envelope) throws MismatchedDimensionException {
        ensureNonNull("envelope", envelope);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope;
        for (int i = 0; i < dim; i++) {
            final double min = envelope.getMinimum(i);
            final double max = envelope.getMaximum(i);
            if (min < ordinates[i])
                ordinates[i] = min;
            if (max > ordinates[i + dim])
                ordinates[i + dim] = max;
        }
        assert isEmpty() || contains(envelope, true);
    }

    /**
     * Tests if a specified coordinate is inside the boundary of this envelope.
     * <p>
     * This method assumes that the specified point uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param position
     *            The point to text.
     * @return {@code true} if the specified coordinates are inside the boundary of this envelope;
     *         {@code false} otherwise.
     * @throws MismatchedDimensionException
     *             if the specified point doesn't have the expected dimension.
     */
    public boolean contains(final DirectPosition position) throws MismatchedDimensionException {
        ensureNonNull("position", position);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("point", position.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, position.getCoordinateReferenceSystem()) : position;
        for (int i = 0; i < dim; i++) {
            final double value = position.getOrdinate(i);
            if (!(value >= ordinates[i]))
                return false;
            if (!(value <= ordinates[i + dim]))
                return false;
            // Use '!' in order to take 'NaN' in account.
        }
        return true;
    }

    /**
     * Returns {@code true} if this envelope completly encloses the specified envelope. If one or
     * more edges from the specified envelope coincide with an edge from this envelope, then this
     * method returns {@code true} only if {@code edgesInclusive} is {@code true}.
     * <p>
     * This method assumes that the specified envelope uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param envelope
     *            The envelope to test for inclusion.
     * @param edgesInclusive
     *            {@code true} if this envelope edges are inclusive.
     * @return {@code true} if this envelope completly encloses the specified one.
     * @throws MismatchedDimensionException
     *             if the specified envelope doesn't have the expected dimension.
     *
     * @see #intersects(Envelope, boolean)
     * @see #equals(Envelope, double)
     *
     * @since 2.2
     */
    public boolean contains(final Envelope envelope, final boolean edgesInclusive)
            throws MismatchedDimensionException {
        ensureNonNull("envelope", envelope);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope;
        for (int i = 0; i < dim; i++) {
            double inner = envelope.getMinimum(i);
            double outer = ordinates[i];
            if (!(edgesInclusive ? inner >= outer : inner > outer)) { // ! is for catching NaN.
                return false;
            }
            inner = envelope.getMaximum(i);
            outer = ordinates[i + dim];
            if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN.
                return false;
            }
        }
        assert intersects(envelope, edgesInclusive);
        return true;
    }

    /**
     * Returns {@code true} if this envelope intersects the specified envelope. If one or more edges
     * from the specified envelope coincide with an edge from this envelope, then this method
     * returns {@code true} only if {@code edgesInclusive} is {@code true}.
     * <p>
     * This method assumes that the specified envelope uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param envelope
     *            The envelope to test for intersection.
     * @param edgesInclusive
     *            {@code true} if this envelope edges are inclusive.
     * @return {@code true} if this envelope intersects the specified one.
     * @throws MismatchedDimensionException
     *             if the specified envelope doesn't have the expected dimension.
     *
     * @see #contains(Envelope, boolean)
     * @see #equals(Envelope, double)
     *
     * @since 2.2
     */
    public boolean intersects(final Envelope envelope, final boolean edgesInclusive)
            throws MismatchedDimensionException {
        ensureNonNull("envelope", envelope);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope;
        for (int i = 0; i < dim; i++) {
            double inner = envelope.getMaximum(i);
            double outer = ordinates[i];
            if (!(edgesInclusive ? inner >= outer : inner > outer)) { // ! is for catching NaN.
                return false;
            }
            inner = envelope.getMinimum(i);
            outer = ordinates[i + dim];
            if (!(edgesInclusive ? inner <= outer : inner < outer)) { // ! is for catching NaN.
                return false;
            }
        }
        return true;
    }

    /**
     * Sets this envelope to the intersection if this envelope with the specified one.
     * <p>
     * This method assumes that the specified envelope uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param envelope
     *            the {@code Envelope} to intersect to this envelope.
     * @throws MismatchedDimensionException
     *             if the specified envelope doesn't have the expected dimension.
     */
    public void intersect(final Envelope envelope) throws MismatchedDimensionException {
        ensureNonNull("envelope", envelope);
        final int dim = ordinates.length / 2;
        AbstractDirectPosition.ensureDimensionMatch("envelope", envelope.getDimension(), dim);
        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope;
        for (int i = 0; i < dim; i++) {
            double min = Math.max(ordinates[i], envelope.getMinimum(i));
            double max = Math.min(ordinates[i + dim], envelope.getMaximum(i));
            if (min > max) {
                // Make an empty envelope (min==max)
                // while keeping it legal (min<=max).
                min = max = 0.5 * (min + max);
            }
            ordinates[i] = min;
            ordinates[i + dim] = max;
        }
    }

    /**
     * Returns a new envelope that encompass only some dimensions of this envelope. This method copy
     * this envelope's ordinates into a new envelope, beginning at dimension <code>lower</code> and
     * extending to dimension <code>upper-1</code>. Thus the dimension of the subenvelope is
     * <code>upper-lower</code>.
     *
     * @param lower
     *            The first dimension to copy, inclusive.
     * @param upper
     *            The last dimension to copy, exclusive.
     * @return The subenvelope.
     * @throws IndexOutOfBoundsException
     *             if an index is out of bounds.
     */
    public GeneralEnvelope getSubEnvelope(final int lower, final int upper)
            throws IndexOutOfBoundsException {
        final int curDim = ordinates.length / 2;
        final int newDim = upper - lower;
        if (lower < 0 || lower > curDim) {
            throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
                    "lower", lower));
        }
        if (newDim < 0 || upper > curDim) {
            throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
                    "upper", upper));
        }
        final GeneralEnvelope envelope = new GeneralEnvelope(newDim);
        System.arraycopy(ordinates, lower, envelope.ordinates, 0, newDim);
        System.arraycopy(ordinates, lower + curDim, envelope.ordinates, newDim, newDim);
        return envelope;
    }

    /**
     * Returns a new envelope with the same values than this envelope minus the specified range of
     * dimensions.
     *
     * @param lower
     *            The first dimension to omit, inclusive.
     * @param upper
     *            The last dimension to omit, exclusive.
     * @return The subenvelope.
     * @throws IndexOutOfBoundsException
     *             if an index is out of bounds.
     */
    public GeneralEnvelope getReducedEnvelope(final int lower, final int upper)
            throws IndexOutOfBoundsException {
        final int curDim = ordinates.length / 2;
        final int rmvDim = upper - lower;
        if (lower < 0 || lower > curDim) {
            throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
                    "lower", lower));
        }
        if (rmvDim < 0 || upper > curDim) {
            throw new IndexOutOfBoundsException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,
                    "upper", upper));
        }
        final GeneralEnvelope envelope = new GeneralEnvelope(curDim - rmvDim);
        System.arraycopy(ordinates, 0, envelope.ordinates, 0, lower);
        System.arraycopy(ordinates, lower, envelope.ordinates, upper, curDim - upper);
        return envelope;
    }

    /**
     * Returns a {@link Rectangle2D} with the same bounds as this {@code Envelope}. This is a
     * convenience method for interoperability with Java2D.
     *
     * @return This envelope as a twp-dimensional rectangle.
     * @throws IllegalStateException
     *             if this envelope is not two-dimensional.
     */
    public Rectangle2D toRectangle2D() throws IllegalStateException {
        if (ordinates.length == 4) {
            return XRectangle2D.createFromExtremums(ordinates[0], ordinates[1], ordinates[2],
                    ordinates[3]);
        } else {
            throw new IllegalStateException(Errors.format(ErrorKeys.NOT_TWO_DIMENSIONAL_$1,
                    getDimension()));
        }
    }

    /**
     * Returns a hash value for this envelope.
     */
    @Override
    public int hashCode() {
        int code = Arrays.hashCode(ordinates);
        if (crs != null) {
            code += crs.hashCode();
        }
        assert code == super.hashCode();
        return code;
    }

    /**
     * Compares the specified object with this envelope for equality.
     */
    @Override
    public boolean equals(final Object object) {
        if (object != null && object.getClass().equals(getClass())) {
            final GeneralEnvelope that = (GeneralEnvelope) object;
            return Arrays.equals(this.ordinates, that.ordinates)
                    && Utilities.equals(this.crs, that.crs);
        }
        return false;
    }

    /**
     * Compares to the specified envelope for equality up to the specified tolerance value. The
     * tolerance value {@code eps} can be either relative to the {@linkplain #getLength envelope
     * length} along each dimension or can be an absolute value (as for example some ground
     * resolution of a {@linkplain org.opengis.coverage.grid.GridCoverage grid coverage}).
     * <p>
     * If {@code relativeToLength} is set to {@code true}, the actual tolerance value for a given
     * dimension <var>i</var> is {@code eps}&times;{@code length} where {@code length} is the
     * maximum of {@linkplain #getLength this envelope length} and the specified envelope length
     * along dimension <var>i</var>.
     * <p>
     * If {@code relativeToLength} is set to {@code false}, the actual tolerance value for a given
     * dimension <var>i</var> is {@code eps}.
     * <p>
     * Relative tolerance value (as opposed to absolute tolerance value) help to workaround the fact
     * that tolerance value are CRS dependent. For example the tolerance value need to be smaller
     * for geographic CRS than for UTM projections, because the former typically has a range of -180
     * to 180° while the later can have a range of thousands of meters.
     * <p>
     * This method assumes that the specified envelope uses the same CRS than this envelope. For
     * performance reason, it will no be verified unless J2SE assertions are enabled.
     *
     * @param envelope
     *            The envelope to compare with.
     * @param eps
     *            The tolerance value to use for numerical comparaisons.
     * @param epsIsRelative
     *            {@code true} if the tolerance value should be relative to axis length, or {@code
     *            false} if it is an absolute value.
     * @return {@code true} if the given object is equals to this envelope up to the given tolerance
     *         value.
     *
     * @see #contains(Envelope, boolean)
     * @see #intersects(Envelope, boolean)
     *
     * @since 2.4
     */
    public boolean equals(final Envelope envelope, final double eps, final boolean epsIsRelative) {
        ensureNonNull("envelope", envelope);
        final int dimension = getDimension();
        if (envelope.getDimension() != dimension) {
            return false;
        }
        assert equalsIgnoreMetadata(crs, envelope.getCoordinateReferenceSystem()) : envelope;
        for (int i = 0; i < dimension; i++) {
            double epsilon;
            if (epsIsRelative) {
                epsilon = Math.max(getSpan(i), envelope.getSpan(i));
                epsilon = (epsilon > 0 && epsilon < Double.POSITIVE_INFINITY) ? epsilon * eps : eps;
            } else {
                epsilon = eps;
            }
            // Comparaison below uses '!' in order to catch NaN values.
            if (!(Math.abs(getMinimum(i) - envelope.getMinimum(i)) <= epsilon && Math
                    .abs(getMaximum(i) - envelope.getMaximum(i)) <= epsilon)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns a deep copy of this envelope.
     *
     * @return A clone of this envelope.
     */
    @Override
    public GeneralEnvelope clone() {
        try {
            GeneralEnvelope e = (GeneralEnvelope) super.clone();
            e.ordinates = e.ordinates.clone();
            return e;
        } catch (CloneNotSupportedException exception) {
            // Should not happen, since we are cloneable.
            throw new AssertionError(exception);
        }
    }
}
TOP

Related Classes of org.geotools.geometry.GeneralEnvelope

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.