001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.coverage.grid;
021:
022: // J2SE dependencies
023: import java.awt.Rectangle;
024: import java.awt.image.Raster;
025: import java.awt.image.RenderedImage;
026: import java.awt.geom.Rectangle2D; // For javadoc
027: import java.io.Serializable;
028: import java.util.Arrays;
029:
030: // OpenGIS dependencies
031: import org.opengis.coverage.grid.GridRange;
032: import org.opengis.coverage.grid.GridGeometry; // For javadoc
033: import org.opengis.coverage.grid.GridCoordinates;
034: import org.opengis.geometry.Envelope;
035:
036: // Geotools dependencies
037: import org.geotools.resources.Utilities;
038: import org.geotools.resources.i18n.Errors;
039: import org.geotools.resources.i18n.ErrorKeys;
040:
041: /**
042: * Defines a range of grid coverage coordinates.
043: *
044: * @since 2.1
045: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GeneralGridRange.java $
046: * @version $Id: GeneralGridRange.java 24925 2007-03-27 20:12:08Z jgarnett $
047: * @author Martin Desruisseaux
048: */
049: public class GeneralGridRange implements GridRange, Serializable {
050: /**
051: * Serial number for interoperability with different versions.
052: */
053: private static final long serialVersionUID = 1452569710967224145L;
054:
055: /**
056: * The lower left corner. Will be created only when first needed.
057: */
058: private transient GeneralGridCoordinates lower;
059:
060: /**
061: * The upper right corner. Will be created only when first needed.
062: */
063: private transient GeneralGridCoordinates upper;
064:
065: /**
066: * Minimum and maximum grid ordinates. The first half contains minimum
067: * ordinates, while the last half contains maximum ordinates.
068: */
069: private final int[] index;
070:
071: /**
072: * Check if ordinate values in the minimum index are less than or
073: * equal to the corresponding ordinate value in the maximum index.
074: *
075: * @throws IllegalArgumentException if an ordinate value in the minimum index is not
076: * less than or equal to the corresponding ordinate value in the maximum index.
077: */
078: private void checkCoherence() throws IllegalArgumentException {
079: final int dimension = index.length / 2;
080: for (int i = 0; i < dimension; i++) {
081: final int lower = index[i];
082: final int upper = index[dimension + i];
083: if (!(lower <= upper)) {
084: throw new IllegalArgumentException(Errors.format(
085: ErrorKeys.BAD_GRID_RANGE_$3, new Integer(i),
086: new Integer(lower), new Integer(upper)));
087: }
088: }
089: }
090:
091: /**
092: * Constructs an initially empty grid range of the specified dimension.
093: */
094: private GeneralGridRange(final int dimension) {
095: index = new int[dimension * 2];
096: }
097:
098: /**
099: * Constructs one-dimensional grid range.
100: *
101: * @param lower The minimal inclusive value.
102: * @param upper The maximal exclusive value.
103: */
104: public GeneralGridRange(final int lower, final int upper) {
105: index = new int[] { lower, upper };
106: checkCoherence();
107: }
108:
109: /**
110: * Constructs a new grid range.
111: *
112: * @param lower The valid minimum inclusive grid coordinate.
113: * The array contains a minimum value for each
114: * dimension of the grid coverage. The lowest
115: * valid grid coordinate is zero.
116: * @param upper The valid maximum exclusive grid coordinate.
117: * The array contains a maximum value for each
118: * dimension of the grid coverage.
119: *
120: * @see #getLowers
121: * @see #getUppers
122: */
123: public GeneralGridRange(final int[] lower, final int[] upper) {
124: if (lower.length != upper.length) {
125: throw new IllegalArgumentException(Errors.format(
126: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
127: lower.length), new Integer(upper.length)));
128: }
129: index = new int[lower.length + upper.length];
130: System.arraycopy(lower, 0, index, 0, lower.length);
131: System.arraycopy(upper, 0, index, lower.length, upper.length);
132: checkCoherence();
133: }
134:
135: /**
136: * Constructs two-dimensional range defined by a {@link Rectangle}.
137: */
138: public GeneralGridRange(final Rectangle rect) {
139: index = new int[] { rect.x, rect.y, rect.x + rect.width,
140: rect.y + rect.height };
141: checkCoherence();
142: }
143:
144: /**
145: * Constructs two-dimensional range defined by a {@link Raster}.
146: */
147: public GeneralGridRange(final Raster raster) {
148: final int x = raster.getMinX();
149: final int y = raster.getMinY();
150: index = new int[] { x, y, x + raster.getWidth(),
151: y + raster.getHeight() };
152: checkCoherence();
153: }
154:
155: /**
156: * Constructs two-dimensional range defined by a {@link RenderedImage}.
157: */
158: public GeneralGridRange(final RenderedImage image) {
159: this (image, 2);
160: }
161:
162: /**
163: * Constructs multi-dimensional range defined by a {@link RenderedImage}.
164: *
165: * @param image The image.
166: * @param dimension Number of dimensions for this grid range.
167: * Dimensions over 2 will be set to the [0..1] range.
168: */
169: GeneralGridRange(final RenderedImage image, final int dimension) {
170: index = new int[dimension * 2];
171: final int x = image.getMinX();
172: final int y = image.getMinY();
173: index[0] = x;
174: index[1] = y;
175: index[dimension + 0] = x + image.getWidth();
176: index[dimension + 1] = y + image.getHeight();
177: Arrays.fill(index, dimension + 2, index.length, 1);
178: checkCoherence();
179: }
180:
181: /**
182: * Cast the specified envelope into a grid range. This is sometime useful after an
183: * envelope has been transformed from "real world" coordinates to grid coordinates
184: * using the "{@linkplain GridGeometry#getGridToCoordinateSystem grid to CRS}" transform.
185: * The floating point values are rounded toward the nearest integers.
186: * <p>
187: * <strong>Note about rounding mode:</strong><br>
188: * It would have been possible to round the {@linkplain Envelope#getMinimum minimal value}
189: * toward {@linkplain Math#floor floor} and the {@linkplain Envelope#getMaximum maximal value}
190: * toward {@linkplain Math#ceil ceil} in order to make sure that the grid range encompass all
191: * the envelope (something similar to what <cite>Java2D</cite> does when casting
192: * {@link Rectangle2D} to {@link Rectangle}). But this approach has an undesirable
193: * side effect: it may changes the image {@linkplain RenderedImage#getWidth width} or
194: * {@linkplain RenderedImage#getHeight height}. For example the range {@code [-0.25 ... 99.75]}
195: * would be casted to {@code [-1 ... 100]}, which leads to unexpected result when using grid
196: * range with image operations like "{@link javax.media.jai.operator.AffineDescriptor Affine}".
197: * For avoiding such changes in size, it is necessary to use the same rounding mode for both
198: * minimal and maximal values. The selected rounding mode is {@linkplain Math#round nearest
199: * integer} in this implementation.
200: *
201: * @since 2.2
202: */
203: public GeneralGridRange(final Envelope envelope) {
204: final int dimension = envelope.getDimension();
205: index = new int[dimension * 2];
206: for (int i = 0; i < dimension; i++) {
207: // See "note about conversion of floating point values to integers" in the JavaDoc.
208: index[i] = (int) Math.round(envelope.getMinimum(i));
209: index[i + dimension] = (int) Math.round(envelope
210: .getMaximum(i));
211: }
212: }
213:
214: /**
215: * Returns the number of dimensions.
216: */
217: public int getDimension() {
218: return index.length / 2;
219: }
220:
221: /**
222: * Returns the valid minimum inclusive grid coordinate along the specified dimension.
223: *
224: * @see #getLowers
225: */
226: public int getLower(final int dimension) {
227: if (dimension < index.length / 2) {
228: return index[dimension];
229: }
230: throw new ArrayIndexOutOfBoundsException(dimension);
231: }
232:
233: /**
234: * Returns the valid maximum exclusive grid coordinate along the specified dimension.
235: *
236: * @see #getUppers
237: */
238: public int getUpper(final int dimension) {
239: if (dimension >= 0) {
240: return index[dimension + index.length / 2];
241: } else
242: throw new ArrayIndexOutOfBoundsException(dimension);
243: }
244:
245: /**
246: * Returns the number of integer grid coordinates along the specified dimension.
247: * This is equals to {@code getUpper(dimension)-getLower(dimension)}.
248: */
249: public int getLength(final int dimension) {
250: return index[dimension + index.length / 2] - index[dimension];
251: }
252:
253: /**
254: * Returns the valid minimum inclusive grid coordinate.
255: * The sequence contains a minimum value for each dimension of the grid coverage.
256: *
257: * @since 2.4
258: */
259: public GridCoordinates getLower() {
260: if (lower == null) {
261: lower = new GeneralGridCoordinates.Immutable(index, 0,
262: index.length / 2);
263: }
264: return lower;
265: }
266:
267: /**
268: * Returns the valid maximum exclusive grid coordinate.
269: * The sequence contains a maximum value for each dimension of the grid coverage.
270: *
271: * @since 2.4
272: */
273: public GridCoordinates getUpper() {
274: if (upper == null) {
275: upper = new GeneralGridCoordinates.Immutable(index,
276: index.length / 2, index.length);
277: }
278: return upper;
279: }
280:
281: /**
282: * Returns the valid minimum inclusive grid coordinates along all dimensions.
283: *
284: * @deprecated Replaced by {@link #getLower}.
285: */
286: public int[] getLowers() {
287: final int[] lo = new int[index.length / 2];
288: System.arraycopy(index, 0, lo, 0, lo.length);
289: return lo;
290: }
291:
292: /**
293: * Returns the valid maximum exclusive grid coordinates along all dimensions.
294: *
295: * @deprecated Replaced by {@link #getUpper}.
296: */
297: public int[] getUppers() {
298: final int[] hi = new int[index.length / 2];
299: System.arraycopy(index, index.length / 2, hi, 0, hi.length);
300: return hi;
301: }
302:
303: /**
304: * Returns a new grid range that encompass only some dimensions of this grid range.
305: * This method copy this grid range's index into a new grid range, beginning at
306: * dimension {@code lower} and extending to dimension {@code upper-1}.
307: * Thus the dimension of the subgrid range is {@code upper-lower}.
308: *
309: * @param lower The first dimension to copy, inclusive.
310: * @param upper The last dimension to copy, exclusive.
311: * @return The subgrid range.
312: * @throws IndexOutOfBoundsException if an index is out of bounds.
313: */
314: public GeneralGridRange getSubGridRange(final int lower,
315: final int upper) {
316: final int curDim = index.length / 2;
317: final int newDim = upper - lower;
318: if (lower < 0 || lower > curDim) {
319: throw new IndexOutOfBoundsException(Errors.format(
320: ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower",
321: new Integer(lower)));
322: }
323: if (newDim < 0 || upper > curDim) {
324: throw new IndexOutOfBoundsException(Errors.format(
325: ErrorKeys.ILLEGAL_ARGUMENT_$2, "upper",
326: new Integer(upper)));
327: }
328: final GeneralGridRange gridRange = new GeneralGridRange(newDim);
329: System.arraycopy(index, lower, gridRange.index, 0, newDim);
330: System.arraycopy(index, lower + curDim, gridRange.index,
331: newDim, newDim);
332: return gridRange;
333: }
334:
335: /**
336: * Returns a {@link Rectangle} with the same bounds as this {@code GeneralGridRange}.
337: * This is a convenience method for interoperability with Java2D.
338: *
339: * @throws IllegalStateException if this grid range is not two-dimensional.
340: */
341: public Rectangle toRectangle() throws IllegalStateException {
342: if (index.length == 4) {
343: return new Rectangle(index[0], index[1], index[2]
344: - index[0], index[3] - index[1]);
345: } else {
346: throw new IllegalStateException(Errors.format(
347: ErrorKeys.NOT_TWO_DIMENSIONAL_$1, new Integer(
348: getDimension())));
349: }
350: }
351:
352: /**
353: * Returns a hash value for this grid range. This value need not remain
354: * consistent between different implementations of the same class.
355: */
356: public int hashCode() {
357: int code = (int) serialVersionUID;
358: if (index != null) {
359: for (int i = index.length; --i >= 0;) {
360: code = code * 31 + index[i];
361: }
362: }
363: return code;
364: }
365:
366: /**
367: * Compares the specified object with this grid range for equality.
368: */
369: public boolean equals(final Object object) {
370: if (object instanceof GeneralGridRange) {
371: final GeneralGridRange that = (GeneralGridRange) object;
372: return Arrays.equals(this .index, that.index);
373: }
374: return false;
375: }
376:
377: /**
378: * Returns a string représentation of this grid range. The returned string is
379: * implementation dependent. It is usually provided for debugging purposes.
380: */
381: public String toString() {
382: final int dimension = index.length / 2;
383: final StringBuffer buffer = new StringBuffer(Utilities
384: .getShortClassName(this ));
385: buffer.append('[');
386: for (int i = 0; i < dimension; i++) {
387: if (i != 0) {
388: buffer.append(", ");
389: }
390: buffer.append(index[i]);
391: buffer.append("..");
392: buffer.append(index[i + dimension]);
393: }
394: buffer.append(']');
395: return buffer.toString();
396: }
397: }
|