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 }