001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/pt/Envelope.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/exse/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     It has been implemented within SEAGIS - An OpenSource implementation of OpenGIS specification
012     (C) 2001, Institut de Recherche pour le D�veloppement (http://sourceforge.net/projects/seagis/)
013     SEAGIS Contacts:  Surveillance de l'Environnement Assist�e par Satellite
014     Institut de Recherche pour le D�veloppement / US-Espace
015     mailto:seasnet@teledetection.fr
016    
017    
018     This library is free software; you can redistribute it and/or
019     modify it under the terms of the GNU Lesser General Public
020     License as published by the Free Software Foundation; either
021     version 2.1 of the License, or (at your option) any later version.
022    
023     This library is distributed in the hope that it will be useful,
024     but WITHOUT ANY WARRANTY; without even the implied warranty of
025     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
026     Lesser General Public License for more details.
027    
028     You should have received a copy of the GNU Lesser General Public
029     License along with this library; if not, write to the Free Software
030     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
031    
032     Contact:
033    
034     Andreas Poth
035     lat/lon GmbH
036     Aennchenstr. 19
037     53115 Bonn
038     Germany
039     E-Mail: poth@lat-lon.de
040    
041     Klaus Greve
042     Department of Geography
043     University of Bonn
044     Meckenheimer Allee 166
045     53115 Bonn
046     Germany
047     E-Mail: klaus.greve@uni-bonn.de
048    
049     
050     ---------------------------------------------------------------------------*/
051    package org.deegree.model.csct.pt;
052    
053    // Miscellaneous
054    import java.awt.geom.Rectangle2D;
055    import java.io.Serializable;
056    import java.util.Arrays;
057    
058    import org.deegree.model.csct.resources.XRectangle2D;
059    import org.deegree.model.csct.resources.css.ResourceKeys;
060    import org.deegree.model.csct.resources.css.Resources;
061    
062    /**
063     * A box defined by two positions. The two positions must have the
064     * same dimension. Each of the ordinate values in the minimum point
065     * must be less than or equal to the corresponding ordinate value
066     * in the maximum point. Please note that these two points may be
067     * outside the valid domain of their coordinate system. (Of course
068     * the points and envelope do not explicitly reference a coordinate
069     * system, but their implicit coordinate system is defined by their
070     * context.)
071     *
072     * @version 1.00
073     * @author OpenGIS (www.opengis.org)
074     * @author Martin Desruisseaux
075     *
076     * @see java.awt.geom.Rectangle2D
077     */
078    public class Envelope implements Dimensioned, Cloneable, Serializable {
079        /**
080         * Serial number for interoperability with different versions.
081         */
082        private static final long serialVersionUID = -3228667532994790309L;
083    
084        /**
085         * Minimum and maximum ordinate values. The first half contains minimum
086         * ordinates, while the last half contains maximum ordinates.
087         */
088        private final double[] ord;
089    
090        /**
091         * Check if ordinate values in the minimum point are less than or
092         * equal to the corresponding ordinate value in the maximum point.
093         *
094         * @throws IllegalArgumentException if an ordinate value in the minimum point is not
095         *         less than or equal to the corresponding ordinate value in the maximum point.
096         */
097        private void checkCoherence()
098                                throws IllegalArgumentException {
099            final int dimension = ord.length / 2;
100            for ( int i = 0; i < dimension; i++ )
101                if ( !( ord[i] <= ord[dimension + i] ) ) // Use '!' in order to catch 'NaN'.
102                    throw new IllegalArgumentException( Resources.format( ResourceKeys.ERROR_ILLEGAL_ENVELOPE_ORDINATE_$1,
103                                                                          new Integer( i ) ) );
104        }
105    
106        /**
107         * Construct a copy of the specified envelope.
108         */
109        private Envelope( final Envelope envelope ) {
110            ord = envelope.ord.clone();
111        }
112    
113        /**
114         * Construct an empty envelope of the specified dimension.
115         * All ordinates are initialized to 0.
116         */
117        public Envelope( final int dimension ) {
118            ord = new double[dimension * 2];
119        }
120    
121        /**
122         * Construct one-dimensional envelope defined by a range of values.
123         *
124         * @param min The minimal value.
125         * @param max The maximal value.
126         */
127        public Envelope( final double min, final double max ) {
128            ord = new double[] { min, max };
129            checkCoherence();
130        }
131    
132        /**
133         * Construct a envelope defined by two positions.
134         *
135         * @param  minCP Minimum ordinate values.
136         * @param  maxCP Maximum ordinate values.
137         * @throws MismatchedDimensionException if the two positions don't have the same dimension.
138         * @throws IllegalArgumentException if an ordinate value in the minimum point is not
139         *         less than or equal to the corresponding ordinate value in the maximum point.
140         */
141        public Envelope( final double[] minCP, final double[] maxCP )
142                                throws MismatchedDimensionException {
143            if ( minCP.length != maxCP.length ) {
144                throw new MismatchedDimensionException( minCP.length, maxCP.length );
145            }
146            ord = new double[minCP.length + maxCP.length];
147            System.arraycopy( minCP, 0, ord, 0, minCP.length );
148            System.arraycopy( maxCP, 0, ord, minCP.length, maxCP.length );
149            checkCoherence();
150        }
151    
152        /**
153         * Construct a envelope defined by two positions.
154         *
155         * @param  minCP Point containing minimum ordinate values.
156         * @param  maxCP Point containing maximum ordinate values.
157         * @throws MismatchedDimensionException if the two positions don't have the same dimension.
158         * @throws IllegalArgumentException if an ordinate value in the minimum point is not
159         *         less than or equal to the corresponding ordinate value in the maximum point.
160         */
161        public Envelope( final CoordinatePoint minCP, final CoordinatePoint maxCP )
162                                throws MismatchedDimensionException {
163            this( minCP.ord, maxCP.ord );
164        }
165    
166        /**
167         * Construct two-dimensional envelope defined by a {@link Rectangle2D}.
168         */
169        public Envelope( final Rectangle2D rect ) {
170            ord = new double[] { rect.getMinX(), rect.getMinY(), rect.getMaxX(), rect.getMaxY() };
171            checkCoherence();
172        }
173    
174        /**
175         * Convenience method for checking the envelope's dimension validity.
176         * This method is usually call for argument checking.
177         *
178         * @param  expectedDimension Expected dimension for this envelope.
179         * @throws MismatchedDimensionException if this envelope doesn't have the expected dimension.
180         */
181        void ensureDimensionMatch( final int expectedDimension )
182                                throws MismatchedDimensionException {
183            final int dimension = getDimension();
184            if ( dimension != expectedDimension ) {
185                throw new MismatchedDimensionException( dimension, expectedDimension );
186            }
187        }
188    
189        /**
190         * Determines whether or not this envelope is empty.
191         * An envelope is non-empty only if it has a length
192         * greater that 0 along all dimensions.
193         */
194        public boolean isEmpty() {
195            final int dimension = ord.length / 2;
196            for ( int i = 0; i < dimension; i++ )
197                if ( !( ord[i] < ord[i + dimension] ) ) // Use '!' in order to catch NaN
198                    return true;
199            return false;
200        }
201    
202        /**
203         * Returns the number of dimensions.
204         */
205        public int getDimension() {
206            return ord.length / 2;
207        }
208    
209        /**
210         * Returns the minimal ordinate
211         * along the specified dimension.
212         */
213        public double getMinimum( final int dimension ) {
214            if ( dimension < ord.length )
215                return ord[dimension];
216            throw new ArrayIndexOutOfBoundsException( dimension );
217        }
218    
219        /**
220         * Returns the maximal ordinate
221         * along the specified dimension.
222         */
223        public double getMaximum( final int dimension ) {
224            if ( dimension >= 0 )
225                return ord[dimension + ord.length / 2];
226            throw new ArrayIndexOutOfBoundsException( dimension );
227        }
228    
229        /**
230         * Returns the center ordinate
231         * along the specified dimension.
232         */
233        public double getCenter( final int dimension ) {
234            return 0.5 * ( ord[dimension] + ord[dimension + ord.length / 2] );
235        }
236    
237        /**
238         * Returns the envelope length along the specified dimension.
239         * This length is equals to the maximum ordinate minus the
240         * minimal ordinate.
241         */
242        public double getLength( final int dimension ) {
243            return ord[dimension + ord.length / 2] - ord[dimension];
244        }
245    
246        /**
247         * Set the envelope's range along the specified dimension.
248         *
249         * @param dimension The dimension to set.
250         * @param minimum   The minimum value along the specified dimension.
251         * @param maximum   The maximum value along the specified dimension.
252         */
253        public void setRange( final int dimension, double minimum, double maximum ) {
254            if ( minimum > maximum ) {
255                // Make an empty envelope (min==max)
256                // while keeping it legal (min<=max).
257                minimum = maximum = 0.5 * ( minimum + maximum );
258            }
259            if ( dimension >= 0 ) {
260                // Do not make any change if 'dimension' is out of range.
261                ord[dimension + ord.length / 2] = maximum;
262                ord[dimension] = minimum;
263            } else
264                throw new ArrayIndexOutOfBoundsException( dimension );
265        }
266    
267        /**
268         * Adds a point to this envelope. The resulting envelope
269         * is the smallest envelope that contains both the original envelope and the
270         * specified point. After adding a point, a call to {@link #contains} with the
271         * added point as an argument will return <code>true</code>, except if one of
272         * the point's ordinates was {@link Double#NaN} (in which case the corresponding
273         * ordinate have been ignored).
274         *
275         * @param  point The point to add.
276         * @throws MismatchedDimensionException if the specified point doesn't have
277         *         the expected dimension.
278         */
279        public void add( final CoordinatePoint point )
280                                throws MismatchedDimensionException {
281            final int dim = ord.length / 2;
282            point.ensureDimensionMatch( dim );
283            for ( int i = 0; i < dim; i++ ) {
284                final double value = point.ord[i];
285                if ( value < ord[i] )
286                    ord[i] = value;
287                if ( value > ord[i + dim] )
288                    ord[i + dim] = value;
289            }
290        }
291    
292        /**
293         * Adds an envelope object to this envelope.
294         * The resulting envelope is the union of the
295         * two <code>Envelope</code> objects.
296         *
297         * @param  envelope the <code>Envelope</code> to add to this envelope.
298         * @throws MismatchedDimensionException if the specified envelope doesn't
299         *         have the expected dimension.
300         */
301        public void add( final Envelope envelope )
302                                throws MismatchedDimensionException {
303            final int dim = ord.length / 2;
304            envelope.ensureDimensionMatch( dim );
305            for ( int i = 0; i < dim; i++ ) {
306                final double min = envelope.ord[i];
307                final double max = envelope.ord[i + dim];
308                if ( min < ord[i] )
309                    ord[i] = min;
310                if ( max > ord[i + dim] )
311                    ord[i + dim] = max;
312            }
313        }
314    
315        /**
316         * Tests if a specified coordinate is inside the boundary of this envelope.
317         *
318         * @param  point The point to text.
319         * @return <code>true</code> if the specified coordinates are inside the boundary
320         *         of this envelope; <code>false</code> otherwise.
321         * @throws MismatchedDimensionException if the specified point doesn't have
322         *         the expected dimension.
323         */
324        public boolean contains( final CoordinatePoint point )
325                                throws MismatchedDimensionException {
326            final int dimension = ord.length / 2;
327            point.ensureDimensionMatch( dimension );
328            for ( int i = 0; i < dimension; i++ ) {
329                final double value = point.ord[i];
330                if ( !( value >= ord[i] ) )
331                    return false;
332                if ( !( value <= ord[i + dimension] ) )
333                    return false;
334                // Use '!' in order to take 'NaN' in account.
335            }
336            return true;
337        }
338    
339        /**
340         * Returns a new envelope representing the intersection of this
341         * <code>Envelope</code> with the specified <code>Envelope</code>.
342         *
343         * @param  envelope The <code>Envelope</code> to intersect with this envelope.
344         * @return The largest envelope contained in both the specified <code>Envelope</code>
345         *         and in this <code>Envelope</code>.
346         * @throws MismatchedDimensionException if the specified envelope doesn't
347         *         have the expected dimension.
348         */
349        public Envelope createIntersection( final Envelope envelope )
350                                throws MismatchedDimensionException {
351            final int dim = ord.length / 2;
352            envelope.ensureDimensionMatch( dim );
353            final Envelope dest = new Envelope( dim );
354            for ( int i = 0; i < dim; i++ ) {
355                double min = Math.max( ord[i], envelope.ord[i] );
356                double max = Math.min( ord[i + dim], envelope.ord[i + dim] );
357                if ( min > max ) {
358                    // Make an empty envelope (min==max)
359                    // while keeping it legal (min<=max).
360                    min = max = 0.5 * ( min + max );
361                }
362                dest.ord[i] = min;
363                dest.ord[i + dim] = max;
364            }
365            return dest;
366        }
367    
368        /**
369         * Returns a new envelope that encompass only some dimensions of this envelope.
370         * This method copy this envelope's ordinates into a new envelope, beginning at
371         * dimension <code>lower</code> and extending to dimension <code>upper-1</code>.
372         * Thus the dimension of the subenvelope is <code>upper-lower</code>.
373         *
374         * @param  lower The first dimension to copy, inclusive.
375         * @param  upper The last  dimension to copy, exclusive.
376         * @return The subenvelope.
377         * @throws IndexOutOfBoundsException if an index is out of bounds.
378         */
379        public Envelope getSubEnvelope( final int lower, final int upper ) {
380            final int curDim = ord.length / 2;
381            final int newDim = upper - lower;
382            if ( lower < 0 || lower > curDim ) {
383                throw new IndexOutOfBoundsException(
384                                                     Resources.format(
385                                                                       ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
386                                                                       "lower", new Integer( lower ) ) );
387            }
388            if ( newDim < 0 || upper > curDim ) {
389                throw new IndexOutOfBoundsException(
390                                                     Resources.format(
391                                                                       ResourceKeys.ERROR_ILLEGAL_ARGUMENT_$2,
392                                                                       "upper", new Integer( upper ) ) );
393            }
394            final Envelope envelope = new Envelope( newDim );
395            System.arraycopy( ord, lower, envelope.ord, 0, newDim );
396            System.arraycopy( ord, lower + curDim, envelope.ord, newDim, newDim );
397            return envelope;
398        }
399    
400        /**
401         * Returns a {@link Rectangle2D} with the same bounds as this <code>Envelope</code>.
402         * This is a convenience method for interoperability with Java2D.
403         *
404         * @throws IllegalStateException if this envelope is not two-dimensional.
405         */
406        public Rectangle2D toRectangle2D()
407                                throws IllegalStateException {
408            if ( ord.length == 4 ) {
409                return new XRectangle2D( ord[0], ord[1], ord[2] - ord[0], ord[3] - ord[1] );
410            }
411            throw new IllegalStateException(
412                                             Resources.format(
413                                                               ResourceKeys.ERROR_NOT_TWO_DIMENSIONAL_$1,
414                                                               new Integer( getDimension() ) ) );
415        }
416    
417        /**
418         * Returns a hash value for this envelope.
419         * This value need not remain consistent between
420         * different implementations of the same class.
421         */
422        public int hashCode() {
423            return CoordinatePoint.hashCode( ord );
424        }
425    
426        /**
427         * Compares the specified object with
428         * this envelope for equality.
429         */
430        public boolean equals( final Object object ) {
431            if ( object instanceof Envelope ) {
432                final Envelope that = (Envelope) object;
433                return Arrays.equals( this.ord, that.ord );
434            }
435            return false;
436        }
437    
438        /**
439         * Returns a deep copy of this envelope.
440         */
441        public Object clone() {
442            return new Envelope( this );
443        }
444    
445        /**
446         * Returns a string representation of this envelope.
447         * The returned string is implementation dependent.
448         * It is usually provided for debugging purposes.
449         */
450        public String toString() {
451            return CoordinatePoint.toString( this, ord );
452        }
453    }