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 }