/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/) SEAGIS Contacts: Surveillance de l'Environnement Assist�e par Satellite Institut de Recherche pour le D�veloppement / US-Espace mailto:seasnet@teledetection.fr 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; either version 2.1 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: klaus.greve@uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.model.csct.pt; // Miscellaneous import java.awt.geom.Rectangle2D; import java.io.Serializable; import java.util.Arrays; import org.deegree.model.csct.resources.XRectangle2D; import org.deegree.model.csct.resources.css.ResourceKeys; import org.deegree.model.csct.resources.css.Resources; /** * A box defined by two positions. The two positions must have the * same dimension. Each of the ordinate values in the minimum point * must be less than or equal to the corresponding ordinate value * in the maximum point. Please note that these two points may be * outside the valid domain of their coordinate system. (Of course * the points and envelope do not explicitly reference a coordinate * system, but their implicit coordinate system is defined by their * context.) * * @version 1.00 * @author OpenGIS (www.opengis.org) * @author Martin Desruisseaux * * @see java.awt.geom.Rectangle2D */ public class Envelope implements Dimensioned, Cloneable, Serializable { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -3228667532994790309L; /** * Minimum and maximum ordinate values. The first half contains minimum * ordinates, while the last half contains maximum ordinates. */ private final double[] ord; /** * Check if ordinate values in the minimum point are less than or * equal to the corresponding ordinate value in the maximum point. * * @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 void checkCoherence() throws IllegalArgumentException { final int dimension = ord.length / 2; for ( int i = 0; i < dimension; i++ ) if ( !( ord[i] <= ord[dimension + i] ) ) // Use '!' in order to catch 'NaN'. throw new IllegalArgumentException( Resources.format( ResourceKeys.ERROR_ILLEGAL_ENVELOPE_ORDINATE_$1, new Integer( i ) ) ); } /** * Construct a copy of the specified envelope. */ private Envelope( final Envelope envelope ) { ord = envelope.ord.clone(); } /** * Construct an empty envelope of the specified dimension. * All ordinates are initialized to 0. */ public Envelope( final int dimension ) { ord = new double[dimension * 2]; } /** * Construct one-dimensional envelope defined by a range of values. * * @param min The minimal value. * @param max The maximal value. */ public Envelope( final double min, final double max ) { ord = new double[] { min, max }; checkCoherence(); } /** * Construct a envelope defined by two positions. * * @param minCP Minimum ordinate values. * @param maxCP 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 Envelope( final double[] minCP, final double[] maxCP ) throws MismatchedDimensionException { if ( minCP.length != maxCP.length ) { throw new MismatchedDimensionException( minCP.length, maxCP.length ); } ord = new double[minCP.length + maxCP.length]; System.arraycopy( minCP, 0, ord, 0, minCP.length ); System.arraycopy( maxCP, 0, ord, minCP.length, maxCP.length ); checkCoherence(); } /** * Construct a envelope defined by two positions. * * @param minCP Point containing minimum ordinate values. * @param maxCP Point containing 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 Envelope( final CoordinatePoint minCP, final CoordinatePoint maxCP ) throws MismatchedDimensionException { this( minCP.ord, maxCP.ord ); } /** * Construct two-dimensional envelope defined by a {@link Rectangle2D}. */ public Envelope( final Rectangle2D rect ) { ord = new double[] { rect.getMinX(), rect.getMinY(), rect.getMaxX(), rect.getMaxY() }; checkCoherence(); } /** * Convenience method for checking the envelope's dimension validity. * This method is usually call for argument checking. * * @param expectedDimension Expected dimension for this envelope. * @throws MismatchedDimensionException if this envelope doesn't have the expected dimension. */ void ensureDimensionMatch( final int expectedDimension ) throws MismatchedDimensionException { final int dimension = getDimension(); if ( dimension != expectedDimension ) { throw new MismatchedDimensionException( dimension, expectedDimension ); } } /** * Determines whether or not this envelope is empty. * An envelope is non-empty only if it has a length * greater that 0 along all dimensions. */ public boolean isEmpty() { final int dimension = ord.length / 2; for ( int i = 0; i < dimension; i++ ) if ( !( ord[i] < ord[i + dimension] ) ) // Use '!' in order to catch NaN return true; return false; } /** * Returns the number of dimensions. */ public int getDimension() { return ord.length / 2; } /** * Returns the minimal ordinate * along the specified dimension. */ public double getMinimum( final int dimension ) { if ( dimension < ord.length ) return ord[dimension]; throw new ArrayIndexOutOfBoundsException( dimension ); } /** * Returns the maximal ordinate * along the specified dimension. */ public double getMaximum( final int dimension ) { if ( dimension >= 0 ) return ord[dimension + ord.length / 2]; throw new ArrayIndexOutOfBoundsException( dimension ); } /** * Returns the center ordinate * along the specified dimension. */ public double getCenter( final int dimension ) { return 0.5 * ( ord[dimension] + ord[dimension + ord.length / 2] ); } /** * Returns the envelope length along the specified dimension. * This length is equals to the maximum ordinate minus the * minimal ordinate. */ public double getLength( final int dimension ) { return ord[dimension + ord.length / 2] - ord[dimension]; } /** * Set 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. */ public void setRange( final int dimension, double minimum, double maximum ) { if ( minimum > maximum ) { // Make an empty envelope (min==max) // while keeping it legal (min<=max). minimum = maximum = 0.5 * ( minimum + maximum ); } if ( dimension >= 0 ) { // Do not make any change if 'dimension' is out of range. ord[dimension + ord.length / 2] = maximum; ord[dimension] = minimum; } else throw new ArrayIndexOutOfBoundsException( dimension ); } /** * 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</code>, except if one of * the point's ordinates was {@link Double#NaN} (in which case the corresponding * ordinate have been ignored). * * @param point The point to add. * @throws MismatchedDimensionException if the specified point doesn't have * the expected dimension. */ public void add( final CoordinatePoint point ) throws MismatchedDimensionException { final int dim = ord.length / 2; point.ensureDimensionMatch( dim ); for ( int i = 0; i < dim; i++ ) { final double value = point.ord[i]; if ( value < ord[i] ) ord[i] = value; if ( value > ord[i + dim] ) ord[i + dim] = value; } } /** * Adds an envelope object to this envelope. * The resulting envelope is the union of the * two <code>Envelope</code> objects. * * @param envelope the <code>Envelope</code> 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 { final int dim = ord.length / 2; envelope.ensureDimensionMatch( dim ); for ( int i = 0; i < dim; i++ ) { final double min = envelope.ord[i]; final double max = envelope.ord[i + dim]; if ( min < ord[i] ) ord[i] = min; if ( max > ord[i + dim] ) ord[i + dim] = max; } } /** * Tests if a specified coordinate is inside the boundary of this envelope. * * @param point The point to text. * @return <code>true</code> if the specified coordinates are inside the boundary * of this envelope; <code>false</code> otherwise. * @throws MismatchedDimensionException if the specified point doesn't have * the expected dimension. */ public boolean contains( final CoordinatePoint point ) throws MismatchedDimensionException { final int dimension = ord.length / 2; point.ensureDimensionMatch( dimension ); for ( int i = 0; i < dimension; i++ ) { final double value = point.ord[i]; if ( !( value >= ord[i] ) ) return false; if ( !( value <= ord[i + dimension] ) ) return false; // Use '!' in order to take 'NaN' in account. } return true; } /** * Returns a new envelope representing the intersection of this * <code>Envelope</code> with the specified <code>Envelope</code>. * * @param envelope The <code>Envelope</code> to intersect with this envelope. * @return The largest envelope contained in both the specified <code>Envelope</code> * and in this <code>Envelope</code>. * @throws MismatchedDimensionException if the specified envelope doesn't * have the expected dimension. */ public Envelope createIntersection( final Envelope envelope ) throws MismatchedDimensionException { final int dim = ord.length / 2; envelope.ensureDimensionMatch( dim ); final Envelope dest = new Envelope( dim ); for ( int i = 0; i < dim; i++ ) { double min = Math.max( ord[i], envelope.ord[i] ); double max = Math.min( ord[i + dim], envelope.ord[i + dim] ); if ( min > max ) { // Make an empty envelope (min==max) // while keeping it legal (min<=max). min = max = 0.5 * ( min + max ); } dest.ord[i] = min; dest.ord[i + dim] = max; } return dest; } /** * 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 Envelope getSubEnvelope( final int lower, final int upper ) { final int curDim = ord.length / 2; final int newDim = upper - lower; if ( lower < 0 || lower > curDim ) { throw new IndexOutOfBoundsException( Resources.format( ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, "lower", new Integer( lower ) ) ); } if ( newDim < 0 || upper > curDim ) { throw new IndexOutOfBoundsException( Resources.format( ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2, "upper", new Integer( upper ) ) ); } final Envelope envelope = new Envelope( newDim ); System.arraycopy( ord, lower, envelope.ord, 0, newDim ); System.arraycopy( ord, lower + curDim, envelope.ord, newDim, newDim ); return envelope; } /** * Returns a {@link Rectangle2D} with the same bounds as this <code>Envelope</code>. * This is a convenience method for interoperability with Java2D. * * @throws IllegalStateException if this envelope is not two-dimensional. */ public Rectangle2D toRectangle2D() throws IllegalStateException { if ( ord.length == 4 ) { return new XRectangle2D( ord[0], ord[1], ord[2] - ord[0], ord[3] - ord[1] ); } throw new IllegalStateException( Resources.format( ResourceKeys.ERROR_NOT_TWO_DIMENSIONAL_$1, new Integer( getDimension() ) ) ); } /** * Returns a hash value for this envelope. * This value need not remain consistent between * different implementations of the same class. */ public int hashCode() { return CoordinatePoint.hashCode( ord ); } /** * Compares the specified object with * this envelope for equality. */ public boolean equals( final Object object ) { if ( object instanceof Envelope ) { final Envelope that = (Envelope) object; return Arrays.equals( this.ord, that.ord ); } return false; } /** * Returns a deep copy of this envelope. */ public Object clone() { return new Envelope( this ); } /** * Returns a string representation of this envelope. * The returned string is implementation dependent. * It is usually provided for debugging purposes. */ public String toString() { return CoordinatePoint.toString( this, ord ); } }