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.Rectangle;
021: import java.awt.geom.AffineTransform;
022: import java.awt.geom.Point2D;
023: import java.awt.geom.Rectangle2D;
024: import java.awt.image.BufferedImage;
025: import java.awt.image.RenderedImage;
026: import java.util.Map;
027: import java.util.HashMap;
028: import java.util.Locale;
029:
030: // OpenGIS dependencies
031: import org.opengis.coverage.CannotEvaluateException;
032: import org.opengis.coverage.grid.GridRange;
033: import org.opengis.metadata.spatial.PixelOrientation;
034: import org.opengis.referencing.FactoryException;
035: import org.opengis.referencing.cs.CoordinateSystem;
036: import org.opengis.referencing.operation.Matrix;
037: import org.opengis.referencing.operation.MathTransform;
038: import org.opengis.referencing.operation.MathTransform2D;
039: import org.opengis.referencing.operation.NoninvertibleTransformException;
040: import org.opengis.referencing.operation.TransformException;
041: import org.opengis.referencing.crs.CoordinateReferenceSystem;
042: import org.opengis.geometry.Envelope;
043: import org.opengis.geometry.MismatchedDimensionException;
044:
045: // Geotools dependencies
046: import org.geotools.geometry.Envelope2D;
047: import org.geotools.referencing.factory.FactoryGroup;
048: import org.geotools.referencing.operation.matrix.MatrixFactory;
049: import org.geotools.referencing.operation.transform.DimensionFilter;
050: import org.geotools.referencing.operation.transform.ProjectiveTransform;
051: import org.geotools.referencing.operation.transform.ConcatenatedTransform;
052: import org.geotools.resources.Utilities;
053: import org.geotools.resources.i18n.Errors;
054: import org.geotools.resources.i18n.ErrorKeys;
055:
056: /**
057: * Describes the valid range of grid coordinates and the math transform, in the special case
058: * where only 2 dimensions are in use. By "in use", we means dimension with more than 1 pixel.
059: * For example a grid size of 512×512×1 pixels can be represented by this
060: * {@code GridGeometry2D} class (some peoples said 2.5D) because a two-dimensional grid
061: * coordinate is enough for referencing a pixel without ambiguity. But a grid size of
062: * 512×512×2 pixels can not be represented by this {@code GridGeometry2D},
063: * because a three-dimensional coordinate is mandatory for referencing a pixel without
064: * ambiguity.
065: *
066: * @since 2.1
067: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridGeometry2D.java $
068: * @version $Id: GridGeometry2D.java 27070 2007-09-19 16:14:49Z desruisseaux $
069: * @author Martin Desruisseaux
070: */
071: public class GridGeometry2D extends GeneralGridGeometry {
072: /**
073: * Serial number for interoperability with different versions.
074: */
075: private static final long serialVersionUID = -3989363771504614419L;
076:
077: /**
078: * Helpers methods for 2D CRS creation. Will be constructed only when first needed.
079: */
080: private static FactoryGroup FACTORY_GROUP;
081:
082: /**
083: * The offset for various pixel orientations. Keys must be upper-case names.
084: *
085: * @todo Uncomment the additional enums if we add those code lists to GeoAPI.
086: */
087: private static Map/*<PixelOrientation, Point2D.Double>*/ORIENTATIONS = new HashMap(
088: 8);
089: static {
090: ORIENTATIONS.put(PixelOrientation.CENTER, new Point2D.Double(
091: 0.0, 0.0));
092: ORIENTATIONS.put(PixelOrientation.UPPER_LEFT,
093: new Point2D.Double(-0.5, -0.5));
094: ORIENTATIONS.put(PixelOrientation.UPPER_RIGHT,
095: new Point2D.Double(0.5, -0.5));
096: ORIENTATIONS.put(PixelOrientation.LOWER_LEFT,
097: new Point2D.Double(-0.5, 0.5));
098: ORIENTATIONS.put(PixelOrientation.LOWER_RIGHT,
099: new Point2D.Double(0.5, 0.5));
100: // ORIENTATIONS.put(PixelOrientation.LEFT, new Point2D.Double(-0.5, 0.0));
101: // ORIENTATIONS.put(PixelOrientation.RIGHT, new Point2D.Double( 0.5, 0.0));
102: // ORIENTATIONS.put(PixelOrientation.UPPER, new Point2D.Double( 0.0, -0.5));
103: // ORIENTATIONS.put(PixelOrientation.LOWER, new Point2D.Double( 0.0, 0.5));
104: }
105:
106: /**
107: * The two-dimensional part of the coordinate reference system.
108: * This is usually (but not always) identical to {@link #getCoordinateReferenceSystem}.
109: */
110: private final CoordinateReferenceSystem crs2D;
111:
112: /**
113: * The first ({@code gridDimensionX}) and second ({@code gridDimensionY}) dimensions of
114: * {@linkplain #getGridRange grid range} with {@linkplain GridRange#getLength length}
115: * greater than 1. Those (<var>x</var>, <var>y</var>) dimensions are usually 0 and 1
116: * respectively.
117: */
118: public final int gridDimensionX, gridDimensionY;
119:
120: /**
121: * The ({@link #gridDimensionX}, {@link #gridDimensionY}) dimensions in the envelope space.
122: * They are the (<var>x</var>, <var>y</var>) dimensions after the
123: * {@linkplain #getGridToCoordinateSystem grid to CRS} transform.
124: * Those dimensions are usually 0 and 1 respectively.
125: */
126: public final int axisDimensionX, axisDimensionY;
127:
128: /**
129: * A math transform mapping only the two first dimensions of {@link #gridToCRS gridToCRS}.
130: */
131: private final MathTransform2D gridToCRS2D;
132:
133: /**
134: * The inverse of {@code gridToCRS2D}.
135: */
136: private final MathTransform2D gridFromCRS2D;
137:
138: /**
139: * Constructs a new grid geometry identical to the specified one except for the CRS.
140: * Note that this constructor just defines the CRS; it does <strong>not</strong> reproject
141: * the envelope. For this reason, this constructor should not be public. It is for internal
142: * use by {@link GridCoverageFactory} only.
143: */
144: GridGeometry2D(final GridGeometry2D gm,
145: final CoordinateReferenceSystem crs) {
146: super (gm, crs);
147: gridDimensionX = gm.gridDimensionX;
148: gridDimensionY = gm.gridDimensionY;
149: axisDimensionX = gm.axisDimensionX;
150: axisDimensionY = gm.axisDimensionY;
151: gridFromCRS2D = gm.gridFromCRS2D;
152: gridToCRS2D = gm.gridToCRS2D;
153: crs2D = createCRS2D();
154: }
155:
156: /**
157: * Constructs a new grid geometry from a math transform. The arguments are passed unchanged
158: * to the {@linkplain GeneralGridGeometry#GeneralGridGeometry(GridRange, MathTransform,
159: * CoordinateReferenceSystem) super-class constructor}. However, they must obey to one
160: * additional constraint: only two dimensions in the grid range can have a
161: * {@linkplain GridRange#getLength length} larger than 1.
162: *
163: * @param gridRange The valid coordinate range of a grid coverage, or {@code null} if none.
164: * The lowest valid grid coordinate is zero for {@link BufferedImage}, but may
165: * be non-zero for arbitrary {@link RenderedImage}. A grid with 512 cells can have a
166: * minimum coordinate of 0 and maximum of 512, with 511 as the highest valid index.
167: * @param gridToCRS The math transform which allows for the transformations
168: * from grid coordinates (pixel's <em>center</em>) to real world earth coordinates.
169: * @param crs The coordinate reference system for the "real world" coordinates, or {@code null}
170: * if unknown. This CRS is given to the {@linkplain #getEnvelope envelope}.
171: *
172: * @throws MismatchedDimensionException if the math transform and the CRS doesn't have
173: * consistent dimensions.
174: * @throws IllegalArgumentException if {@code gridRange} has more than 2 dimensions with
175: * a {@linkplain GridRange#getLength length} larger than 1, or if the math transform
176: * can't transform coordinates in the domain of the specified grid range.
177: *
178: * @see RenderedImage#getMinX
179: * @see RenderedImage#getMinY
180: * @see RenderedImage#getWidth
181: * @see RenderedImage#getHeight
182: *
183: * @since 2.2
184: */
185: public GridGeometry2D(final GridRange gridRange,
186: final MathTransform gridToCRS,
187: final CoordinateReferenceSystem crs)
188: throws IllegalArgumentException,
189: MismatchedDimensionException {
190: super (gridRange, gridToCRS, crs);
191: final int[] dimensions;
192: dimensions = new int[4];
193: gridToCRS2D = getMathTransform2D(gridToCRS, gridRange,
194: dimensions);
195: gridFromCRS2D = inverse(gridToCRS2D);
196: gridDimensionX = dimensions[0];
197: gridDimensionY = dimensions[1];
198: axisDimensionX = dimensions[2];
199: axisDimensionY = dimensions[3];
200: crs2D = createCRS2D();
201: }
202:
203: /**
204: * Constructs a new grid geometry from an envelope. This constructors applies the same heuristic
205: * rules than the {@linkplain GeneralGridGeometry#GeneralGridGeometry(GridRange,Envelope)
206: * super-class constructor}. However, they must obey to one additional constraint: only two
207: * dimensions in the grid range can have a {@linkplain GridRange#getLength length} larger than
208: * 1.
209: *
210: * @param gridRange The valid coordinate range of a grid coverage.
211: * @param userRange The corresponding coordinate range in user coordinate.
212: *
213: * @throws IllegalArgumentException if {@code gridRange} has more than 2 dimensions with
214: * a {@linkplain GridRange#getLength length} larger than 1.
215: * @throws MismatchedDimensionException if the grid range and the CRS doesn't have
216: * consistent dimensions.
217: *
218: * @since 2.2
219: */
220: public GridGeometry2D(final GridRange gridRange,
221: final Envelope userRange) throws IllegalArgumentException,
222: MismatchedDimensionException {
223: this (gridRange, userRange, null, false, true);
224: }
225:
226: /**
227: * Constructs a new grid geometry from an envelope. The argument are passed unchanged to the
228: * {@linkplain GeneralGridGeometry#GeneralGridGeometry(GridRange,Envelope,boolean[],boolean)
229: * super-class constructor}. However, they must obey to one additional constraint:
230: * only two dimensions in the grid range can have a {@linkplain GridRange#getLength length}
231: * larger than 1.
232: *
233: * @param gridRange The valid coordinate range of a grid coverage.
234: * @param userRange The corresponding coordinate range in user coordinate.
235: * @param reverse Tells for each axis in <cite>user</cite> space whatever or not its
236: * direction should be reversed. A {@code null} value reverse no axis.
237: * @param swapXY If {@code true}, then the two first axis will be interchanged.
238: *
239: * @throws IllegalArgumentException if {@code gridRange} has more than 2 dimensions with
240: * a {@linkplain GridRange#getLength length} larger than 1.
241: * @throws MismatchedDimensionException if the grid range and the CRS doesn't have
242: * consistent dimensions.
243: *
244: * @since 2.2
245: */
246: public GridGeometry2D(final GridRange gridRange,
247: final Envelope userRange, final boolean[] reverse,
248: final boolean swapXY) throws IllegalArgumentException,
249: MismatchedDimensionException {
250: this (gridRange, userRange, reverse, swapXY, false);
251: }
252:
253: /**
254: * Implementation of heuristic constructors.
255: */
256: private GridGeometry2D(final GridRange gridRange,
257: final Envelope userRange, final boolean[] reverse,
258: final boolean swapXY, final boolean automatic)
259: throws IllegalArgumentException,
260: MismatchedDimensionException {
261: super (gridRange, userRange, reverse, swapXY, automatic);
262: final int[] dimensions;
263: dimensions = new int[4];
264: gridToCRS2D = getMathTransform2D(gridToCRS, gridRange,
265: dimensions);
266: gridFromCRS2D = inverse(gridToCRS2D);
267: gridDimensionX = dimensions[0];
268: gridDimensionY = dimensions[1];
269: axisDimensionX = dimensions[2];
270: axisDimensionY = dimensions[3];
271: crs2D = createCRS2D();
272: }
273:
274: /**
275: * Constructs a new two-dimensional grid geometry. A math transform will be computed
276: * automatically with an inverted <var>y</var> axis (i.e. {@code gridRange} and
277: * {@code userRange} are assumed to have <var>y</var> axis in opposite direction).
278: *
279: * @param gridRange The valid coordinate range of a grid coverage.
280: * Increasing <var>x</var> values goes right and
281: * increasing <var>y</var> values goes <strong>down</strong>.
282: * @param userRange The corresponding coordinate range in user coordinate.
283: * Increasing <var>x</var> values goes right and
284: * increasing <var>y</var> values goes <strong>up</strong>.
285: * This rectangle must contains entirely all pixels, i.e.
286: * the rectangle's upper left corner must coincide with
287: * the upper left corner of the first pixel and the rectangle's
288: * lower right corner must coincide with the lower right corner
289: * of the last pixel.
290: */
291: public GridGeometry2D(final Rectangle gridRange,
292: final Rectangle2D userRange) {
293: this (new GeneralGridRange(gridRange), getMathTransform(
294: gridRange, userRange), (CoordinateReferenceSystem) null);
295: }
296:
297: /**
298: * Workaround for RFE #4093999 ("Relax constraint on placement of this()/super()
299: * call in constructors").
300: */
301: private static MathTransform getMathTransform(
302: final Rectangle gridRange, final Rectangle2D userRange) {
303: final double scaleX = userRange.getWidth()
304: / gridRange.getWidth();
305: final double scaleY = userRange.getHeight()
306: / gridRange.getHeight();
307: final double transX = userRange.getMinX() - gridRange.x
308: * scaleX;
309: final double transY = userRange.getMaxY() + gridRange.y
310: * scaleY;
311: final AffineTransform tr = new AffineTransform(scaleX, 0, 0,
312: -scaleY, transX, transY);
313: tr.translate(0.5, 0.5); // Maps to pixel center
314: return ProjectiveTransform.create(tr);
315: }
316:
317: /**
318: * Returns the math transform for two dimensions of the specified transform.
319: *
320: * @param gridRange The grid range.
321: * @param transform The transform.
322: * @param axis An array of length 4 initialized to 0. This is the array where to store
323: * {@link #axisDimensionX} and {@link #axisDimensionY} values. This argument is
324: * actually a workaround for a Java language limitation (no multiple return values).
325: * If we could, we should returns directly the {@code dimensions} array computed in
326: * the body of this method.
327: * @return The {@link MathTransform2D} part of {@code transform}.
328: * @throws IllegalArgumentException if the 2D part is not separable.
329: */
330: private static MathTransform2D getMathTransform2D(
331: final MathTransform transform, final GridRange gridRange,
332: final int[] axis) throws IllegalArgumentException {
333: axis[1] = axis[3] = 1;
334: if (transform == null || transform instanceof MathTransform2D) {
335: return (MathTransform2D) transform;
336: }
337: /*
338: * Finds the axis for the two dimensional parts. We infer them from the grid range.
339: * If no grid range were specified, then we assume that they are the 2 first dimensions.
340: */
341: final DimensionFilter filter = new DimensionFilter();
342: if (gridRange != null) {
343: final int dimension = gridRange.getDimension();
344: for (int i = 0; i < dimension; i++) {
345: if (gridRange.getLength(i) > 1) {
346: filter.addSourceDimension(i);
347: }
348: }
349: } else {
350: filter.addSourceDimensionRange(0, 2);
351: }
352: Exception cause = null;
353: int[] dimensions = filter.getSourceDimensions();
354: /*
355: * Select a math transform that operate only on the two dimensions choosen above.
356: * If such a math transform doesn't have exactly 2 output dimensions, then select
357: * the same output dimensions than the input ones.
358: */
359: MathTransform candidate;
360: if (dimensions.length == 2) {
361: System.arraycopy(dimensions, 0, axis, 0, 2);
362: try {
363: candidate = filter.separate(transform);
364: if (candidate.getTargetDimensions() != 2) {
365: filter.clear();
366: filter.addSourceDimensions(dimensions);
367: filter.addTargetDimensions(dimensions);
368: candidate = filter.separate(transform);
369: }
370: dimensions = filter.getTargetDimensions();
371: System.arraycopy(dimensions, 0, axis, 2, 2);
372: try {
373: return (MathTransform2D) candidate;
374: } catch (ClassCastException exception) {
375: cause = exception;
376: }
377: } catch (FactoryException exception) {
378: cause = exception;
379: }
380: }
381: IllegalArgumentException e = new IllegalArgumentException(
382: Errors.format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
383: e.initCause(cause); // TODO: Move in constructor's argument when we
384: throw e; // will be allowed to compile for J2SE 1.5.
385: }
386:
387: /**
388: * Inverses the specified math transform. This method is invoked by constructors only. It wraps
389: * {@link NoninvertibleTransformException} into {@link IllegalArgumentException}, since failures
390: * to inverse a transform are caused by an illegal user-supplied transform.
391: *
392: * @throws IllegalArgumentException if the transform is non-invertible.
393: */
394: private static MathTransform2D inverse(
395: final MathTransform2D gridToCRS2D)
396: throws IllegalArgumentException {
397: if (gridToCRS2D == null) {
398: return null;
399: } else
400: try {
401: return (MathTransform2D) gridToCRS2D.inverse();
402: } catch (NoninvertibleTransformException exception) {
403: final IllegalArgumentException e;
404: e = new IllegalArgumentException(Errors.format(
405: ErrorKeys.BAD_TRANSFORM_$1, Utilities
406: .getShortClassName(gridToCRS2D)));
407: e.initCause(exception); // TODO: move into contructor call with J2SE 1.5.
408: throw e;
409: }
410: }
411:
412: /**
413: * Constructs the two-dimensional CRS. This is usually identical to the user-supplied CRS.
414: * However, the user is allowed to specify a wider CRS (for example a 3D one which includes
415: * a time axis), in which case we infer which axis apply to the 2D image, and constructs a
416: * 2D CRS with only those axis.
417: *
418: * @return The coordinate reference system, or {@code null} if none.
419: */
420: private CoordinateReferenceSystem createCRS2D() {
421: if (!super .isDefined(CRS)) {
422: return null;
423: }
424: final CoordinateReferenceSystem crs = super
425: .getCoordinateReferenceSystem();
426: if (FACTORY_GROUP == null) {
427: FACTORY_GROUP = FactoryGroup.createInstance(null);
428: // No need to synchronize: this is not a big deal
429: // if two FactoryGroup instances are created.
430: }
431: final CoordinateReferenceSystem crs2D;
432: try {
433: crs2D = FACTORY_GROUP.separate(crs, new int[] {
434: axisDimensionX, axisDimensionY });
435: } catch (FactoryException exception) {
436: final InvalidGridGeometryException e = new InvalidGridGeometryException(
437: Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "crs",
438: crs.getName()));
439: e.initCause(exception); // TODO: inline in the constructor with J2SE 1.5.
440: throw e;
441: }
442: assert crs2D.getCoordinateSystem().getDimension() == 2 : crs2D;
443: return crs2D;
444: }
445:
446: /**
447: * Returns the two-dimensional part of this grid geometry CRS. This is usually (but not
448: * always) identical to the {@linkplain #getCoordinateReferenceSystem full CRS}.
449: *
450: * @return The coordinate reference system (never {@code null}).
451: * @throws InvalidGridGeometryException if this grid geometry has no CRS (i.e.
452: * <code>{@linkplain #isDefined isDefined}({@linkplain #CRS CRS})</code>
453: * returned {@code false}).
454: *
455: * @see #getCoordinateReferenceSystem
456: *
457: * @since 2.2
458: */
459: public CoordinateReferenceSystem getCoordinateReferenceSystem2D()
460: throws InvalidGridGeometryException {
461: if (crs2D != null) {
462: assert isDefined(CRS);
463: return crs2D;
464: }
465: assert !isDefined(CRS);
466: throw new InvalidGridGeometryException(Errors
467: .format(ErrorKeys.UNSPECIFIED_CRS));
468: }
469:
470: /**
471: * Returns the two-dimensional bounding box for the coverage domain in coordinate reference
472: * system coordinates. If the coverage envelope has more than two dimensions, only the
473: * dimensions used in the underlying rendered image are returned.
474: *
475: * @return The bounding box in "real world" coordinates (never {@code null}).
476: * @throws InvalidGridGeometryException if this grid geometry has no envelope (i.e.
477: * <code>{@linkplain #isDefined isDefined}({@linkplain #ENVELOPE ENVELOPE})</code>
478: * returned {@code false}).
479: *
480: * @see #getEnvelope
481: */
482: public Envelope2D getEnvelope2D()
483: throws InvalidGridGeometryException {
484: if (envelope != null && !envelope.isNull()) {
485: assert isDefined(ENVELOPE);
486: return new Envelope2D(crs2D, envelope
487: .getMinimum(axisDimensionX), envelope
488: .getMinimum(axisDimensionY), envelope
489: .getLength(axisDimensionX), envelope
490: .getLength(axisDimensionY));
491: }
492: assert !isDefined(ENVELOPE);
493: throw new InvalidGridGeometryException(
494: Errors
495: .format(gridToCRS == null ? ErrorKeys.UNSPECIFIED_TRANSFORM
496: : ErrorKeys.UNSPECIFIED_IMAGE_SIZE));
497: }
498:
499: /**
500: * Returns the two-dimensional part of the {@linkplain #getGridRange grid range}
501: * as a rectangle.
502: *
503: * @return The grid range (never {@code null}).
504: * @throws InvalidGridGeometryException if this grid geometry has no grid range (i.e.
505: * <code>{@linkplain #isDefined isDefined}({@linkplain #GRID_RANGE GRID_RANGE})</code>
506: * returned {@code false}).
507: *
508: * @see #getGridRange
509: * @see RenderedImage#getMinX
510: * @see RenderedImage#getMinY
511: * @see RenderedImage#getWidth
512: * @see RenderedImage#getHeight
513: */
514: public Rectangle getGridRange2D()
515: throws InvalidGridGeometryException {
516: if (gridRange != null) {
517: assert isDefined(GRID_RANGE);
518: return new Rectangle(gridRange.getLower(gridDimensionX),
519: gridRange.getLower(gridDimensionY), gridRange
520: .getLength(gridDimensionX), gridRange
521: .getLength(gridDimensionY));
522: }
523: assert !isDefined(GRID_RANGE);
524: throw new InvalidGridGeometryException(Errors
525: .format(ErrorKeys.UNSPECIFIED_IMAGE_SIZE));
526: }
527:
528: /**
529: * @deprecated Renamed as {@link #getGridToCRS2D()}.
530: */
531: public MathTransform2D getGridToCoordinateSystem2D()
532: throws InvalidGridGeometryException {
533: return getGridToCRS2D();
534: }
535:
536: /**
537: * Returns a math transform for the two dimensional part. This is a convenience method for
538: * working on horizontal data while ignoring vertical or temporal dimensions.
539: *
540: * @return The transform which allows for the transformations from grid coordinates
541: * to real world earth coordinates, operating only on two dimensions.
542: * The returned transform is often an instance of {@link AffineTransform}, which
543: * make it convenient for interoperability with Java2D.
544: * @throws InvalidGridGeometryException if a two-dimensional transform is not available
545: * for this grid geometry.
546: *
547: * @see #getGridToCRS
548: *
549: * @since 2.3
550: */
551: public MathTransform2D getGridToCRS2D()
552: throws InvalidGridGeometryException {
553: if (gridToCRS2D != null) {
554: return gridToCRS2D;
555: }
556: throw new InvalidGridGeometryException(Errors
557: .format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
558: }
559:
560: /**
561: * Returns a math transform for the two dimensional part. This method is similar
562: * to {@link #getGridToCRS2D()} except that the transform may maps a pixel corner
563: * instead of pixel center.
564: *
565: * @param orientation The pixel part to map. The default value is
566: * {@link PixelOrientation#CENTER CENTER}.
567: * @return The transform which allows for the transformations from grid coordinates
568: * to real world earth coordinates.
569: * @throws InvalidGridGeometryException if a two-dimensional transform is not available
570: * for this grid geometry.
571: *
572: * @since 2.3
573: */
574: public MathTransform2D getGridToCRS2D(
575: final PixelOrientation orientation) {
576: final int xdim = (gridDimensionX < gridDimensionY) ? 0 : 1;
577: return (MathTransform2D) translate(getGridToCRS2D(),
578: orientation, xdim, xdim ^ 1);
579: }
580:
581: /**
582: * Returns a math transform mapping the specified pixel part.
583: *
584: * @param orientation The pixel part to map. The default value is
585: * {@link PixelOrientation#CENTER CENTER}.
586: * @return The transform which allows for the transformations from grid coordinates
587: * to real world earth coordinates.
588: * @throws InvalidGridGeometryException if a transform is not available
589: * for this grid geometry.
590: *
591: * @since 2.3
592: */
593: public MathTransform getGridToCRS(final PixelOrientation orientation) {
594: return translate(getGridToCRS(), orientation, gridDimensionX,
595: gridDimensionY);
596: }
597:
598: /**
599: * Translates the specified math transform according the specified pixel orientation.
600: * The {@code gridToCRS} math transform is assumed maps the pixel centers.
601: */
602: private static MathTransform translate(
603: final MathTransform gridToCRS,
604: final PixelOrientation orientation,
605: final int gridDimensionX, final int gridDimensionY) {
606: if (PixelOrientation.CENTER.equals(orientation)) {
607: return gridToCRS;
608: }
609: final Point2D.Double offset = getDirectPixelTranslation(orientation);
610: final int dimension = gridToCRS.getSourceDimensions();
611: final Matrix matrix = MatrixFactory.create(dimension + 1);
612: matrix.setElement(gridDimensionX, dimension, offset.x);
613: matrix.setElement(gridDimensionY, dimension, offset.y);
614: return ConcatenatedTransform.create(ProjectiveTransform
615: .create(matrix), gridToCRS);
616: }
617:
618: /**
619: * Like {@link #getPixelTranslation} but without cloning the returned value.
620: */
621: private static Point2D.Double getDirectPixelTranslation(
622: final PixelOrientation orientation)
623: throws IllegalArgumentException {
624: final Point2D.Double offset = (Point2D.Double) ORIENTATIONS
625: .get(orientation);
626: if (offset == null) {
627: throw new IllegalArgumentException(Errors.format(
628: ErrorKeys.ILLEGAL_ARGUMENT_$2, "orientation",
629: orientation));
630: }
631: return offset;
632: }
633:
634: /**
635: * Returns the specified position relative to the pixel center.
636: * This method returns a value from the following table:
637: * <p>
638: * <table>
639: * <tr><th>Pixel orientation</th> <th> x </th><th> y </th></tr>
640: * <tr><td>{@link PixelOrientation#CENTER CENTER}</td> <td> 0.0</td><td> 0.0</td></tr>
641: * <tr><td>{@link PixelOrientation#UPPER_LEFT UPPER_LEFT}</td> <td>-0.5</td><td>-0.5</td></tr>
642: * <tr><td>{@link PixelOrientation#UPPER_RIGHT UPPER_RIGHT}</td><td>+0.5</td><td>-0.5</td></tr>
643: * <tr><td>{@link PixelOrientation#LOWER_LEFT LOWER_LEFT}</td> <td>-0.5</td><td>+0.5</td></tr>
644: * <tr><td>{@link PixelOrientation#LOWER_RIGHT LOWER_RIGHT}</td><td>+0.5</td><td>+0.5</td></tr>
645: * </table>
646: *
647: * @param orientation The pixel orientation.
648: * @return The position relative to the pixel center.
649: * @throws IllegalArgumentException if the specified orientation is not known.
650: * @since 2.4
651: */
652: public static Point2D getPixelTranslation(
653: final PixelOrientation orientation)
654: throws IllegalArgumentException {
655: return (Point2D) getDirectPixelTranslation(orientation).clone();
656: }
657:
658: /**
659: * Transforms a point using the inverse of {@link #getGridToCRS2D()}.
660: *
661: * @param point The point in logical coordinate system.
662: * @return A new point in the grid coordinate system.
663: * @throws InvalidGridGeometryException if a two-dimensional inverse
664: * transform is not available for this grid geometry.
665: * @throws CannotEvaluateException if the transformation failed.
666: */
667: final Point2D inverseTransform(final Point2D point)
668: throws InvalidGridGeometryException {
669: if (gridFromCRS2D != null) {
670: try {
671: return gridFromCRS2D.transform(point, null);
672: } catch (TransformException exception) {
673: throw new CannotEvaluateException(Errors.format(
674: ErrorKeys.CANT_EVALUATE_$1,
675: AbstractGridCoverage.toString(point, Locale
676: .getDefault()), exception));
677: }
678: }
679: throw new InvalidGridGeometryException(Errors
680: .format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
681: }
682:
683: /**
684: * Returns the pixel coordinate of a rectangle containing the
685: * specified geographic area. If the rectangle can't be computed,
686: * then this method returns {@code null}.
687: */
688: final Rectangle inverseTransform(Rectangle2D bounds) {
689: if (bounds != null && gridFromCRS2D != null) {
690: try {
691: bounds = org.geotools.referencing.CRS.transform(
692: gridFromCRS2D, bounds, null);
693: final int xmin = (int) Math
694: .floor(bounds.getMinX() - 0.5);
695: final int ymin = (int) Math
696: .floor(bounds.getMinY() - 0.5);
697: final int xmax = (int) Math
698: .ceil(bounds.getMaxX() - 0.5);
699: final int ymax = (int) Math
700: .ceil(bounds.getMaxY() - 0.5);
701: return new Rectangle(xmin, ymin, xmax - xmin, ymax
702: - ymin);
703: } catch (TransformException exception) {
704: // Ignore, since this method is invoked from 'GridCoverage.prefetch' only.
705: // It doesn't matter if the transformation failed; 'prefetch' is just a hint.
706: }
707: }
708: return null;
709: }
710:
711: /**
712: * Compares the specified object with this grid geometry for equality.
713: */
714: public boolean equals(final Object object) {
715: if (super .equals(object)) {
716: final GridGeometry2D that = (GridGeometry2D) object;
717: return this .gridDimensionX == that.gridDimensionX
718: && this .gridDimensionY == that.gridDimensionY
719: && this .axisDimensionX == that.axisDimensionX
720: && this .axisDimensionY == that.axisDimensionY;
721: }
722: return false;
723: }
724: }
|