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: package org.geotools.coverage.grid;
018:
019: // J2SE dependencies
020: import java.awt.geom.AffineTransform; // For javadoc
021: import java.awt.image.BufferedImage; // For javadoc
022: import java.awt.image.RenderedImage; // For javadoc
023: import java.io.Serializable;
024:
025: // OpenGIS dependencies
026: import org.opengis.coverage.grid.GridRange;
027: import org.opengis.coverage.grid.GridGeometry;
028: import org.opengis.referencing.datum.PixelInCell;
029: import org.opengis.referencing.operation.MathTransform;
030: import org.geotools.referencing.operation.transform.ConcatenatedTransform;
031: import org.opengis.referencing.crs.CoordinateReferenceSystem;
032: import org.opengis.geometry.Envelope;
033: import org.opengis.geometry.MismatchedDimensionException;
034:
035: // Geotools dependencies
036: import org.geotools.geometry.GeneralEnvelope;
037: import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
038: import org.geotools.referencing.operation.transform.ProjectiveTransform;
039: import org.geotools.resources.Utilities;
040: import org.geotools.resources.i18n.Errors;
041: import org.geotools.resources.i18n.ErrorKeys;
042:
043: /**
044: * Describes the valid range of grid coordinates and the math transform to transform grid
045: * coordinates to real world coordinates. Grid geometries contains:
046: * <p>
047: * <ul>
048: * <li>An optional {@linkplain GridRange grid range}, usually inferred from the
049: * {@linkplain RenderedImage rendered image} size.</li>
050: * <li>An optional "grid to CRS" {@linkplain MathTransform transform}, which may be inferred
051: * from the grid range and the envelope.</li>
052: * <li>An optional {@linkplain Envelope envelope}, which may be inferred from the grid range
053: * and the "grid to CRS" transform.</li>
054: * <li>An optional {@linkplain CoordinateReferenceSystem coordinate reference system} to be
055: * given to the envelope.</li>
056: * </ul>
057: * <p>
058: * All grid geometry attributes are optional because some of them may be inferred from a wider
059: * context. For example a grid geometry know nothing about {@linkplain RenderedImage rendered
060: * images}, but {@link GridCoverage2D} do. Consequently, the later may infer the {@linkplain
061: * GridRange grid range} by itself.
062: * <p>
063: * By default, any request for an undefined attribute will thrown an
064: * {@link InvalidGridGeometryException}. In order to check if an attribute is defined,
065: * use {@link #isDefined}.
066: *
067: * @since 2.1
068: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GeneralGridGeometry.java $
069: * @version $Id: GeneralGridGeometry.java 26984 2007-09-14 18:13:49Z desruisseaux $
070: * @author Martin Desruisseaux
071: * @author Alessio Fabiani
072: */
073: public class GeneralGridGeometry implements GridGeometry, Serializable {
074: /**
075: * Serial number for interoperability with different versions.
076: */
077: private static final long serialVersionUID = 124700383873732132L;
078:
079: /**
080: * A bitmask to specify the validity of the {@linkplain #getCoordinateReferenceSystem
081: * coordinate reference system}. This is given as an argument to the {@link #isDefined}
082: * method.
083: *
084: * @since 2.2
085: */
086: public static final int CRS = 1;
087:
088: /**
089: * A bitmask to specify the validity of the {@linkplain #getEnvelope envelope}.
090: * This is given as an argument to the {@link #isDefined} method.
091: *
092: * @since 2.2
093: */
094: public static final int ENVELOPE = 2;
095:
096: /**
097: * A bitmask to specify the validity of the {@linkplain #getGridRange grid range}.
098: * This is given as an argument to the {@link #isDefined} method.
099: *
100: * @since 2.2
101: */
102: public static final int GRID_RANGE = 4;
103:
104: /**
105: * A bitmask to specify the validity of the {@linkplain #getGridToCoordinateSystem grid to CRS}
106: * transform. This is given as an argument to the {@link #isDefined} method.
107: *
108: * @since 2.2
109: */
110: public static final int GRID_TO_CRS = 8;
111:
112: /**
113: * A buffer of math transforms created by {@link #getHalfPixelTranslation}.
114: * Each element in this array will be created when first needed.
115: */
116: private static final MathTransform[] translations = new MathTransform[8];
117:
118: /**
119: * The valid coordinate range of a grid coverage, or {@code null} if none. The lowest valid
120: * grid coordinate is zero for {@link BufferedImage}, but may be non-zero for arbitrary
121: * {@link RenderedImage}. A grid with 512 cells can have a minimum coordinate of 0 and
122: * maximum of 512, with 511 as the highest valid index.
123: *
124: * @see RenderedImage#getMinX
125: * @see RenderedImage#getMinY
126: * @see RenderedImage#getWidth
127: * @see RenderedImage#getHeight
128: */
129: protected final GridRange gridRange;
130:
131: /**
132: * The envelope, which is usually the {@linkplain #gridRange grid range}
133: * {@linkplain #gridToCRS transformed} to real world coordinates. This
134: * envelope contains the {@linkplain CoordinateReferenceSystem coordinate
135: * reference system} of "real world" coordinates.
136: * <p>
137: * This field should be considered as private because envelopes are mutable, and we want to make
138: * sure that envelopes are cloned before to be returned to the user. Only {@link GridGeometry2D}
139: * and {@link GridCoverage2D} access directly to this field (read only) for performance reason.
140: *
141: * @since 2.2
142: */
143: final GeneralEnvelope envelope;
144:
145: /**
146: * The math transform (usually an affine transform), or {@code null} if none.
147: * This math transform maps {@linkplain PixelInCell#CELL_CENTER pixel center}
148: * to "real world" coordinate using the following line:
149: *
150: * <pre>gridToCRS.transform(pixels, point);</pre>
151: */
152: protected final MathTransform gridToCRS;
153:
154: /**
155: * Constructs a new grid geometry identical to the specified one except for the CRS.
156: * Note that this constructor just defines the CRS; it does <strong>not</strong> reproject
157: * the envelope. For this reason, this constructor should not be public. It is for internal
158: * use by {@link GridCoverageFactory} only.
159: */
160: GeneralGridGeometry(final GeneralGridGeometry gm,
161: final CoordinateReferenceSystem crs) {
162: gridRange = gm.gridRange;
163: gridToCRS = gm.gridToCRS;
164: envelope = new GeneralEnvelope(gm.envelope);
165: envelope.setCoordinateReferenceSystem(crs);
166: }
167:
168: /**
169: * Constructs a new grid geometry from a {@linkplain MathTransform math transform}
170: * mapping {@linkplain PixelInCell#CELL_CENTER pixel center}. This is the most general
171: * constructor, the one that gives the maximal control over the grid geometry to be created.
172: *
173: * @param gridRange The valid coordinate range of a grid coverage, or {@code null} if none.
174: * @param gridToCRS The math transform which allows for the transformations from grid
175: * coordinates (pixel's <em>center</em>) to real world earth coordinates.
176: * May be {@code null}, but this is not recommanded.
177: * @param crs The coordinate reference system for the "real world" coordinates, or
178: * {@code null} if unknown. This CRS is given to the
179: * {@linkplain #getEnvelope envelope}.
180: *
181: * @throws MismatchedDimensionException if the math transform or the CRS doesn't have
182: * consistent dimensions.
183: * @throws IllegalArgumentException if the math transform can't transform coordinates
184: * in the domain of the specified grid range.
185: *
186: * @since 2.2
187: */
188: public GeneralGridGeometry(final GridRange gridRange,
189: final MathTransform gridToCRS,
190: final CoordinateReferenceSystem crs)
191: throws MismatchedDimensionException,
192: IllegalArgumentException {
193: this .gridRange = gridRange;
194: this .gridToCRS = gridToCRS;
195: if (gridRange != null && gridToCRS != null) {
196: envelope = new GeneralEnvelope(gridRange,
197: PixelInCell.CELL_CENTER, gridToCRS, crs);
198: } else if (crs != null) {
199: envelope = new GeneralEnvelope(crs);
200: envelope.setToNull();
201: } else {
202: envelope = null;
203: }
204: }
205:
206: /**
207: * Constructs a new grid geometry from an {@linkplain Envelope envelope}. An {@linkplain
208: * AffineTransform affine transform} will be computed automatically from the specified
209: * envelope using heuristic rules described in {@link GridToEnvelopeMapper} javadoc.
210: * More specifically, heuristic rules are applied for:
211: * <p>
212: * <ul>
213: * <li>{@linkplain GridToEnvelopeMapper#getSwapXY axis swapping}</li>
214: * <li>{@linkplain GridToEnvelopeMapper#getReverseAxis axis reversal}</li>
215: * </ul>
216: *
217: * @param gridRange The valid coordinate range of a grid coverage.
218: * @param userRange The corresponding coordinate range in user coordinate. This rectangle must
219: * contains entirely all pixels, i.e. the rectangle's upper left corner must
220: * coincide with the upper left corner of the first pixel and the rectangle's
221: * lower right corner must coincide with the lower right corner of the last
222: * pixel.
223: *
224: * @throws MismatchedDimensionException if the grid range and the envelope doesn't have
225: * consistent dimensions.
226: *
227: * @since 2.2
228: */
229: public GeneralGridGeometry(final GridRange gridRange,
230: final Envelope userRange)
231: throws MismatchedDimensionException {
232: this (gridRange, userRange, null, false, true);
233: }
234:
235: /**
236: * Constructs a new grid geometry from an {@linkplain Envelope envelope}. This convenience
237: * constructor delegates the work to {@link GridToEnvelopeMapper}; see its javadoc for details.
238: * <p>
239: * If this convenience constructor do not provides suffisient control on axis order or reversal,
240: * then an affine transform shall be created explicitly and the grid geometry shall be created
241: * using the {@linkplain #GeneralGridGeometry(GridRange,MathTransform,CoordinateReferenceSystem)
242: * constructor expecting a math transform} argument.
243: *
244: * @param gridRange The valid coordinate range of a grid coverage.
245: * @param userRange The corresponding coordinate range in user coordinate. This envelope must
246: * contains entirely all pixels, i.e. the envelope's upper left corner must
247: * coincide with the upper left corner of the first pixel and the envelope's
248: * lower right corner must coincide with the lower right corner of the last
249: * pixel.
250: * @param reverse Tells for each axis in <cite>user</cite> space whatever or not its direction
251: * should be reversed. A {@code null} value reverse no axis. Callers will
252: * typically set {@code reverse[1]} to {@code true} in order to reverse the
253: * <var>y</var> axis direction.
254: * @param swapXY If {@code true}, then the two first axis will be interchanged. Callers will
255: * typically set this argument to {@code true} when the geographic coordinate
256: * system has axis in the (<var>y</var>,<var>x</var>) order. The {@code reverse}
257: * parameter then apply to axis after the swap.
258: *
259: * @throws MismatchedDimensionException if the grid range and the envelope doesn't have
260: * consistent dimensions.
261: *
262: * @since 2.2
263: */
264: public GeneralGridGeometry(final GridRange gridRange,
265: final Envelope userRange, final boolean[] reverse,
266: final boolean swapXY) throws MismatchedDimensionException {
267: this (gridRange, userRange, reverse, swapXY, false);
268: }
269:
270: /**
271: * Implementation of heuristic constructors.
272: */
273: GeneralGridGeometry(final GridRange gridRange,
274: final Envelope userRange, final boolean[] reverse,
275: final boolean swapXY, final boolean automatic)
276: throws MismatchedDimensionException {
277: this .gridRange = gridRange;
278: this .envelope = new GeneralEnvelope(userRange);
279: final GridToEnvelopeMapper mapper = new GridToEnvelopeMapper(
280: gridRange, userRange);
281: if (!automatic) {
282: mapper.setReverseAxis(reverse);
283: mapper.setSwapXY(swapXY);
284: }
285: gridToCRS = mapper.createTransform();
286: }
287:
288: /**
289: * Returns the number of dimensions.
290: */
291: public int getDimension() {
292: if (gridToCRS != null) {
293: return gridToCRS.getSourceDimensions();
294: }
295: return getGridRange().getDimension();
296: }
297:
298: /**
299: * Returns the "real world" coordinate reference system.
300: *
301: * @return The coordinate reference system (never {@code null}).
302: * @throws InvalidGridGeometryException if this grid geometry has no CRS (i.e.
303: * <code>{@linkplain #isDefined isDefined}({@linkplain #CRS})</code>
304: * returned {@code false}).
305: *
306: * @see GridGeometry2D#getCoordinateReferenceSystem2D
307: *
308: * @since 2.2
309: */
310: public CoordinateReferenceSystem getCoordinateReferenceSystem()
311: throws InvalidGridGeometryException {
312: if (envelope != null) {
313: final CoordinateReferenceSystem crs = envelope
314: .getCoordinateReferenceSystem();
315: if (crs != null) {
316: assert isDefined(CRS);
317: return crs;
318: }
319: }
320: assert !isDefined(CRS);
321: throw new InvalidGridGeometryException(Errors
322: .format(ErrorKeys.UNSPECIFIED_CRS));
323: }
324:
325: /**
326: * Returns the bounding box of "real world" coordinates for this grid geometry. This envelope is
327: * the {@linkplain #getGridRange grid range} {@linkplain #getGridToCoordinateSystem transformed}
328: * to the "real world" coordinate system.
329: *
330: * @return The bounding box in "real world" coordinates (never {@code null}).
331: * @throws InvalidGridGeometryException if this grid geometry has no envelope (i.e.
332: * <code>{@linkplain #isDefined isDefined}({@linkplain #ENVELOPE})</code>
333: * returned {@code false}).
334: *
335: * @see GridGeometry2D#getEnvelope2D
336: */
337: public Envelope getEnvelope() throws InvalidGridGeometryException {
338: if (envelope != null && !envelope.isNull()) {
339: assert isDefined(ENVELOPE);
340: return (Envelope) envelope.clone();
341: }
342: assert !isDefined(ENVELOPE);
343: throw new InvalidGridGeometryException(
344: Errors
345: .format(gridToCRS == null ? ErrorKeys.UNSPECIFIED_TRANSFORM
346: : ErrorKeys.UNSPECIFIED_IMAGE_SIZE));
347: }
348:
349: /**
350: * Returns the valid coordinate range of a grid coverage. The lowest valid grid coordinate is
351: * zero for {@link BufferedImage}, but may be non-zero for arbitrary {@link RenderedImage}. A
352: * grid with 512 cells can have a minimum coordinate of 0 and maximum of 512, with 511 as the
353: * highest valid index.
354: *
355: * @return The grid range (never {@code null}).
356: * @throws InvalidGridGeometryException if this grid geometry has no grid range (i.e.
357: * <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_RANGE})</code>
358: * returned {@code false}).
359: *
360: * @see GridGeometry2D#getGridRange2D
361: */
362: public GridRange getGridRange() throws InvalidGridGeometryException {
363: if (gridRange != null) {
364: assert isDefined(GRID_RANGE);
365: return gridRange;
366: }
367: assert !isDefined(GRID_RANGE);
368: throw new InvalidGridGeometryException(Errors
369: .format(ErrorKeys.UNSPECIFIED_IMAGE_SIZE));
370: }
371:
372: /**
373: * @deprecated Renamed as {@link #getGridToCRS()}.
374: */
375: public MathTransform getGridToCoordinateSystem()
376: throws InvalidGridGeometryException {
377: return getGridToCRS();
378: }
379:
380: /**
381: * Returns the transform from grid coordinates to real world earth coordinates.
382: * The transform is often an affine transform. The coordinate reference system of the
383: * real world coordinates is given by
384: * {@link org.opengis.coverage.Coverage#getCoordinateReferenceSystem}.
385: * <p>
386: * <strong>Note:</strong> OpenGIS requires that the transform maps <em>pixel centers</em>
387: * to real world coordinates. This is different from some other systems that map pixel's
388: * upper left corner.
389: *
390: * @return The transform (never {@code null}).
391: * @throws InvalidGridGeometryException if this grid geometry has no transform (i.e.
392: * <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_TO_CRS})</code>
393: * returned {@code false}).
394: *
395: * @see GridGeometry2D#getGridToCRS2D()
396: *
397: * @since 2.3
398: */
399: public MathTransform getGridToCRS()
400: throws InvalidGridGeometryException {
401: if (gridToCRS != null) {
402: assert isDefined(GRID_TO_CRS);
403: return gridToCRS;
404: }
405: assert !isDefined(GRID_TO_CRS);
406: throw new InvalidGridGeometryException(Errors
407: .format(ErrorKeys.UNSPECIFIED_TRANSFORM));
408: }
409:
410: /**
411: * Returns the transform from grid coordinates to real world earth coordinates.
412: * This is similar to {@link #getGridToCRS()} except that the transform may maps
413: * other parts than {@linkplain PixelInCell#CELL_CENTER pixel center}.
414: *
415: * @param halfPixel The pixel part to map.
416: * @return The transform (never {@code null}).
417: * @throws InvalidGridGeometryException if this grid geometry has no transform (i.e.
418: * <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_TO_CRS})</code>
419: * returned {@code false}).
420: *
421: * @since 2.3
422: */
423: public MathTransform getGridToCRS(final PixelInCell halfPixel)
424: throws InvalidGridGeometryException {
425: final MathTransform gridToCRS = getGridToCoordinateSystem();
426: if (PixelInCell.CELL_CENTER.equals(halfPixel)) {
427: return gridToCRS;
428: }
429: if (!PixelInCell.CELL_CORNER.equals(halfPixel)) {
430: throw new IllegalArgumentException(Errors.format(
431: ErrorKeys.ILLEGAL_ARGUMENT_$2, "halfPixel",
432: halfPixel));
433: }
434: return ConcatenatedTransform
435: .create(getHalfPixelTranslation(gridToCRS
436: .getSourceDimensions()), gridToCRS);
437: }
438:
439: /**
440: * Returns an affine transform holding a translation from the
441: * {@linkplain PixelInCell#CELL_CENTER pixel center} to the
442: * {@linkplain PixelInCell#CELL_CORNER pixel corner}. The
443: * translation terms are set to exactly -0.5.
444: *
445: * @param dimension The dimension.
446: */
447: private static MathTransform getHalfPixelTranslation(
448: final int dimension) {
449: synchronized (translations) {
450: if (dimension < translations.length) {
451: final MathTransform candidate = translations[dimension];
452: if (candidate != null) {
453: return candidate;
454: }
455: }
456: final MathTransform mt = ProjectiveTransform
457: .createTranslation(dimension, -0.5);
458: if (dimension < translations.length) {
459: translations[dimension] = mt;
460: }
461: return mt;
462: }
463: }
464:
465: /**
466: * Returns {@code true} if all the parameters specified by the argument are set.
467: *
468: * @param bitmask Any combinaison of {@link #CRS}, {@link #ENVELOPE}, {@link #GRID_RANGE}
469: * and {@link #GRID_TO_CRS}.
470: * @return {@code true} if all specified attributes are defined (i.e. invoking the
471: * corresponding method will not thrown an {@link InvalidGridGeometryException}).
472: * @throws IllegalArgumentException if the specified bitmask is not a combinaison of known
473: * masks.
474: *
475: * @since 2.2
476: */
477: public boolean isDefined(final int bitmask)
478: throws IllegalArgumentException {
479: if ((bitmask & ~(CRS | ENVELOPE | GRID_RANGE | GRID_TO_CRS)) != 0) {
480: throw new IllegalArgumentException(Errors.format(
481: ErrorKeys.ILLEGAL_ARGUMENT_$2, "bitmask",
482: new Integer(bitmask)));
483: }
484: return ((bitmask & CRS) == 0 || (envelope != null && envelope
485: .getCoordinateReferenceSystem() != null))
486: && ((bitmask & ENVELOPE) == 0 || (envelope != null && !envelope
487: .isNull()))
488: && ((bitmask & GRID_RANGE) == 0 || (gridRange != null))
489: && ((bitmask & GRID_TO_CRS) == 0 || (gridToCRS != null));
490: }
491:
492: /**
493: * Returns a hash value for this grid geometry. This value need not remain
494: * consistent between different implementations of the same class.
495: */
496: public int hashCode() {
497: int code = (int) serialVersionUID;
498: if (gridToCRS != null) {
499: code += gridToCRS.hashCode();
500: }
501: if (gridRange != null) {
502: code += gridRange.hashCode();
503: }
504: // We do not check the envelope, since it usually has
505: // a determinist relationship with other attributes.
506: return code;
507: }
508:
509: /**
510: * Compares the specified object with this grid geometry for equality.
511: */
512: public boolean equals(final Object object) {
513: if (object != null && object.getClass().equals(getClass())) {
514: final GeneralGridGeometry that = (GeneralGridGeometry) object;
515: return Utilities.equals(this .gridRange, that.gridRange)
516: && Utilities.equals(this .gridToCRS, that.gridToCRS)
517: && Utilities.equals(this .envelope, that.envelope);
518: }
519: return false;
520: }
521:
522: /**
523: * Returns a string representation of this grid geometry. The returned string
524: * is implementation dependent. It is usually provided for debugging purposes.
525: */
526: public String toString() {
527: final StringBuffer buffer = new StringBuffer(Utilities
528: .getShortClassName(this ));
529: buffer.append('[');
530: buffer.append(gridRange);
531: buffer.append(", ");
532: buffer.append(gridToCRS);
533: buffer.append(']');
534: return buffer.toString();
535: }
536: }
|