001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2002, 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.processing.operation;
018:
019: // J2SE dependencies
020: import java.awt.RenderingHints;
021: import java.awt.geom.AffineTransform;
022: import java.awt.image.renderable.ParameterBlock;
023: import java.util.List;
024: import java.util.Locale;
025: import java.util.logging.Level;
026: import java.util.logging.LogRecord;
027:
028: import javax.media.jai.ImageLayout;
029: import javax.media.jai.Interpolation;
030: import javax.media.jai.JAI;
031: import javax.media.jai.PlanarImage;
032: import javax.media.jai.RenderedOp;
033: import javax.media.jai.Warp;
034:
035: import org.geotools.coverage.GridSampleDimension;
036: import org.geotools.coverage.grid.GeneralGridRange;
037: import org.geotools.coverage.grid.GridCoverage2D;
038: import org.geotools.coverage.grid.GridGeometry2D;
039: import org.geotools.coverage.processing.AbstractProcessor;
040: import org.geotools.coverage.processing.CannotReprojectException;
041: import org.geotools.factory.Hints;
042: import org.geotools.geometry.GeneralEnvelope;
043: import org.geotools.image.ImageWorker;
044: import org.geotools.referencing.CRS;
045: import org.geotools.referencing.ReferencingFactoryFinder;
046: import org.geotools.referencing.operation.matrix.XAffineTransform;
047: import org.geotools.referencing.operation.transform.DimensionFilter;
048: import org.geotools.referencing.operation.transform.IdentityTransform;
049: import org.geotools.referencing.operation.transform.WarpTransform2D;
050: import org.geotools.resources.XArray;
051: import org.geotools.resources.coverage.CoverageUtilities;
052: import org.geotools.resources.i18n.ErrorKeys;
053: import org.geotools.resources.i18n.Errors;
054: import org.geotools.resources.i18n.Logging;
055: import org.geotools.resources.i18n.LoggingKeys;
056: import org.geotools.resources.image.ImageUtilities;
057: import org.opengis.coverage.grid.GridRange;
058: import org.opengis.referencing.FactoryException;
059: import org.opengis.referencing.crs.CoordinateReferenceSystem;
060: import org.opengis.referencing.operation.CoordinateOperationFactory;
061: import org.opengis.referencing.operation.MathTransform;
062: import org.opengis.referencing.operation.MathTransform2D;
063: import org.opengis.referencing.operation.MathTransformFactory;
064: import org.opengis.referencing.operation.TransformException;
065: import org.opengis.geometry.Envelope;
066:
067: /**
068: * Implementation of the {@link Resample} operation. This implementation is provided as a
069: * separated class for two purpose: avoid loading this code before needed and provide some
070: * way to check if a grid coverages is a result of a resample operation.
071: *
072: * @since 2.2
073: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/operation/Resampler2D.java $
074: * @version $Id: Resampler2D.java 28130 2007-11-29 11:26:25Z afabiani $
075: * @author Martin Desruisseaux
076: */
077: final class Resampler2D extends GridCoverage2D {
078: /**
079: * The logging level for defails about resampling operation applied.
080: */
081: private static final Level LOGGING_LEVEL = Level.FINE;
082:
083: /**
084: * Constructs a new grid coverage for the specified grid geometry.
085: *
086: * @param source The source for this grid coverage.
087: * @param image The image.
088: * @param geometry The grid geometry (including the new CRS).
089: */
090: private Resampler2D(final GridCoverage2D source,
091: final PlanarImage image, final GridGeometry2D geometry,
092: GridSampleDimension[] sampleDimensions) {
093: super (source.getName(), image, geometry, sampleDimensions,
094: new GridCoverage2D[] { source }, null);
095: }
096:
097: /**
098: * Creates a new coverage with a different coordinate reference reference system. If a
099: * grid geometry is supplied, only its {@linkplain GridGeometry2D#getRange grid range}
100: * and {@linkplain GridGeometry2D#getGridToCoordinateSystem grid to CRS} transform are
101: * taken in account.
102: *
103: * @param sourceCoverage The source grid coverage.
104: * @param targetCRS Coordinate reference system for the new grid coverage, or {@code null}.
105: * @param targetGG The target grid geometry, or {@code null} for default.
106: * @param interpolation The interpolation to use.
107: * @param hints The rendering hints. This is usually provided by
108: * {@link AbstractProcessor}. This method will looks for
109: * {@link Hints#COORDINATE_OPERATION_FACTORY} and
110: * {@link Hints#JAI_INSTANCE} keys.
111: * @return The new grid coverage, or {@code sourceCoverage} if no resampling was needed.
112: * @throws FactoryException if a transformation step can't be created.
113: * @throws TransformException if a transformation failed.
114: */
115: public static GridCoverage2D reproject(
116: GridCoverage2D sourceCoverage,
117: CoordinateReferenceSystem targetCRS,
118: GridGeometry2D targetGG, final Interpolation interpolation,
119: final Hints hints) throws FactoryException,
120: TransformException {
121: /*
122: * Grid range and "grid to CRS" transform are the only grid geometry informations used
123: * by this method. If they are not available, this is equivalent to not providing grid
124: * geometry at all. Set to null, since null value is what the remaining code will check
125: * for.
126: */
127: if (targetGG != null) {
128: if (!targetGG.isDefined(GridGeometry2D.GRID_RANGE)
129: && !targetGG.isDefined(GridGeometry2D.GRID_TO_CRS)) {
130: targetGG = null;
131: }
132: }
133: final boolean automaticGG = (targetGG == null);
134: final boolean automaticGR = automaticGG
135: || !targetGG.isDefined(GridGeometry2D.GRID_RANGE);
136: /*
137: * If the source coverage is already the result of a previous "Resample" operation,
138: * go up in the chain and check if a previously computed image could fits (i.e. the
139: * requested resampling may be the inverse of a previous resampling). This method
140: * may stop immediately if a suitable image is found. Otherwise, we will resample
141: * the original image instead of the user-supplied one, in order to reduce the amount
142: * of intermediate steps (and maybe give a chance to the garbage collector to collect
143: * the user-supplied image).
144: */
145: boolean sameGG, sameCRS;
146: GridGeometry2D sourceGG;
147: CoordinateReferenceSystem sourceCRS;
148: while (true) {
149: sourceGG = (GridGeometry2D) sourceCoverage
150: .getGridGeometry(); // TODO: remove cast with J2SE 1.5.
151: sourceCRS = sourceCoverage.getCoordinateReferenceSystem();
152: if (targetCRS == null) {
153: targetCRS = sourceCRS;
154: }
155: sameGG = (targetGG == null || equivalent(targetGG, sourceGG));
156: sameCRS = CRS.equalsIgnoreMetadata(targetCRS, sourceCRS);
157: if (sameGG && sameCRS) {
158: return sourceCoverage;
159: }
160: if (sourceCoverage instanceof Resampler2D) {
161: final List sources = sourceCoverage.getSources();
162: if (sources.size() != 1) {
163: // Should not happen, but test anyway.
164: throw new AssertionError(sources);
165: }
166: sourceCoverage = (GridCoverage2D) sources.get(0);
167: continue;
168: }
169: break;
170: }
171: /*
172: * Preparing the source image
173: */
174: PlanarImage sourceImage = PlanarImage
175: .wrapRenderedImage(sourceCoverage.getRenderedImage());
176: // 0==nothing changes, 1==index color expanded,
177: // 2==taken geophysics, 3==taken non-geophysics
178: final int actionTaken = CoverageUtilities
179: .prepareSourcesForGCOperation(sourceCoverage,
180: interpolation, false, hints);
181: ParameterBlock paramBlk = new ParameterBlock();
182: switch (actionTaken) {
183: case 1: {
184: sourceImage = new ImageWorker(sourceImage)
185: .forceComponentColorModel().getPlanarImage();
186: break;
187: }
188: case 2: {
189: // in this case we need to go back the geophysics view of the
190: // source coverage
191: sourceCoverage = sourceCoverage.geophysics(true);
192: sourceImage = PlanarImage.wrapRenderedImage(sourceCoverage
193: .getRenderedImage());
194: break;
195: }
196: case 3: {
197: sourceCoverage = sourceCoverage.geophysics(false);
198: break;
199: }
200: }
201: paramBlk.addSource(sourceImage);
202: /*
203: * Hints management
204: */
205: RenderingHints targetHints = ImageUtilities
206: .getRenderingHints(sourceImage);
207: if (targetHints == null) {
208: targetHints = new RenderingHints(hints);
209: } else if (hints != null) {
210: targetHints.add(hints);
211: }
212: /*
213: * The source coverage is now selected and will not change anymore. Gets
214: * the JAI instance and factories to use from the rendering hints.
215: */
216: final JAI processor;
217: if (true) {
218: final Object property = (hints != null) ? hints
219: .get(Hints.JAI_INSTANCE) : null;
220: if (property instanceof JAI) {
221: processor = (JAI) property;
222: } else {
223: processor = JAI.getDefaultInstance();
224: }
225: }
226: final CoordinateOperationFactory factory = ReferencingFactoryFinder
227: .getCoordinateOperationFactory(hints);
228: final MathTransformFactory mtFactory = ReferencingFactoryFinder
229: .getMathTransformFactory(hints);
230: /*
231: * Computes the INVERSE of the math transform from [Source Grid] to [Target Grid].
232: * The transform will be computed using the following path:
233: *
234: * Target Grid --> Target CRS --> Source CRS --> Source Grid
235: * ^ ^ ^
236: * step 1 step 2 step 3
237: *
238: * If source and target CRS are equal, a shorter path is used. This special
239: * case is needed because 'sourceCRS' and 'targetCRS' may be null.
240: *
241: * Target Grid --> Common CRS --> Source Grid
242: * ^ ^
243: * step 1 step 3
244: */
245: final MathTransform step1, step2, step3, allSteps, allSteps2D;
246: if (sameCRS) {
247: /*
248: * Note: targetGG should not be null, otherwise the code that computed 'sameCRS'
249: * should have already detected that this resample is not doing anything.
250: */
251: if (!targetGG.isDefined(GridGeometry2D.GRID_TO_CRS)) {
252: step1 = sourceGG.getGridToCRS(); // Really sourceGG, not targetGG
253: step2 = IdentityTransform.create(step1
254: .getTargetDimensions());
255: step3 = step1.inverse();
256: allSteps = IdentityTransform.create(step1
257: .getSourceDimensions());
258: targetGG = new GridGeometry2D(targetGG.getGridRange(),
259: step1, targetCRS);
260: } else {
261: step1 = targetGG.getGridToCRS();
262: step2 = IdentityTransform.create(step1
263: .getTargetDimensions());
264: step3 = sourceGG.getGridToCRS().inverse();
265: allSteps = mtFactory.createConcatenatedTransform(step1,
266: step3);
267: if (!targetGG.isDefined(GridGeometry2D.GRID_RANGE)) {
268: /*
269: * If the target grid range was not explicitely specified, a grid range will be
270: * automatically computed in such a way that it will maps to the same envelope
271: * (at least approximatively).
272: */
273: Envelope gridRange;
274: gridRange = toEnvelope(sourceGG.getGridRange());
275: gridRange = CRS.transform(allSteps.inverse(),
276: gridRange);
277: targetGG = new GridGeometry2D(new GeneralGridRange(
278: gridRange), step1, targetCRS);
279: }
280: }
281: } else {
282: if (sourceCRS == null) {
283: throw new CannotReprojectException(Errors
284: .format(ErrorKeys.UNSPECIFIED_CRS));
285: }
286: final Envelope sourceEnvelope;
287: final GeneralEnvelope targetEnvelope;
288: step2 = factory.createOperation(targetCRS, sourceCRS)
289: .getMathTransform();
290: step3 = sourceGG.getGridToCRS().inverse();
291: sourceEnvelope = sourceCoverage.getEnvelope();
292: targetEnvelope = CRS.transform(step2.inverse(),
293: sourceEnvelope);
294: targetEnvelope.setCoordinateReferenceSystem(targetCRS);
295: /*
296: * If the target GridGeometry is incomplete, provides default
297: * values for the missing fields. Three cases may occurs:
298: *
299: * - User provided no GridGeometry at all. Then, constructs an image of the same size
300: * than the source image and set an envelope big enough to contains the projected
301: * coordinates. The transform will derivate from the grid range and the envelope.
302: *
303: * - User provided only a grid range. Then, set an envelope big enough to contains
304: * the projected coordinates. The transform will derivate from the grid range and
305: * the envelope.
306: *
307: * - User provided only a "grid to coordinate system" transform. Then, transform the
308: * projected envelope to "grid units" using the specified transform, and create a
309: * grid range big enough to hold the result.
310: */
311: if (targetGG == null) {
312: targetGG = new GridGeometry2D(sourceGG.getGridRange(),
313: targetEnvelope);
314: step1 = targetGG.getGridToCRS();
315: } else if (!targetGG.isDefined(GridGeometry2D.GRID_TO_CRS)) {
316: targetGG = new GridGeometry2D(targetGG.getGridRange(),
317: targetEnvelope);
318: step1 = targetGG.getGridToCRS();
319: } else {
320: step1 = targetGG.getGridToCRS();
321: if (!targetGG.isDefined(GridGeometry2D.GRID_RANGE)) {
322: final GeneralEnvelope gridRange;
323: gridRange = CRS.transform(step1.inverse(),
324: targetEnvelope);
325: for (int i = gridRange.getDimension(); --i >= 0;) {
326: /*
327: * According OpenGIS specification, GridGeometry maps pixel's center.
328: * But the bounding box was for all pixels, not pixel's centers. Offset
329: * by 0.5 (use +0.5 for maximum too, not -0.5, since maximum is exclusive).
330: */
331: gridRange.setRange(i,
332: gridRange.getMinimum(i) + 0.5,
333: gridRange.getMaximum(i) + 0.5);
334: }
335: targetGG = new GridGeometry2D(new GeneralGridRange(
336: gridRange), step1, targetCRS);
337: }
338: }
339: /*
340: * Computes the final transform.
341: */
342: if (step1.equals(step3.inverse())) {
343: allSteps = step2;
344: } else {
345: allSteps = mtFactory.createConcatenatedTransform(
346: mtFactory.createConcatenatedTransform(step1,
347: step2), step3);
348: }
349: }
350: allSteps2D = getMathTransform2D(allSteps, mtFactory, targetGG);
351: if (!(allSteps2D instanceof MathTransform2D)) {
352: // Should not happen with Geotools implementations. May happen
353: // with some external implementations, but should stay unusual.
354: throw new TransformException(Errors
355: .format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
356: }
357: /*
358: * Prepare the parameter block for the image to be created. Prepare also
359: * rendering hints, which contains mostly indications about tiles layout.
360: * The xmin, xmax, ymin and ymax bounds are relative to the target image.
361: */
362: // final PlanarImage sourceImage =
363: final GridRange targetGR = targetGG.getGridRange();
364: final int xAxis = targetGG.gridDimensionX;
365: final int yAxis = targetGG.gridDimensionY;
366: final int xmin = targetGR.getLower(xAxis);
367: final int xmax = targetGR.getUpper(xAxis);
368: final int ymin = targetGR.getLower(yAxis);
369: final int ymax = targetGR.getUpper(yAxis);
370: final GridRange sourceGR = sourceGG.getGridRange();
371: final int xminS = sourceGR.getLower(xAxis);
372: final int xmaxS = sourceGR.getUpper(xAxis);
373: final int yminS = sourceGR.getLower(yAxis);
374: final int ymaxS = sourceGR.getUpper(yAxis);
375: ImageLayout layout = (ImageLayout) targetHints
376: .get(JAI.KEY_IMAGE_LAYOUT);
377: if (layout != null) {
378: layout = (ImageLayout) layout.clone();
379: } else {
380: layout = new ImageLayout(sourceImage);
381: layout.unsetImageBounds();
382: layout.unsetTileLayout();
383: // At this point, only the color model and sample model are left valids.
384: }
385: if ((layout.getValidMask() & (ImageLayout.MIN_X_MASK
386: | ImageLayout.MIN_Y_MASK | ImageLayout.WIDTH_MASK | ImageLayout.HEIGHT_MASK)) == 0) {
387: layout.setMinX(targetGR.getLower(xAxis));
388: layout.setMinY(targetGR.getLower(yAxis));
389: layout.setWidth(targetGR.getLength(xAxis));
390: layout.setHeight(targetGR.getLength(yAxis));
391: }
392: if ((layout.getValidMask() & (ImageLayout.TILE_WIDTH_MASK
393: | ImageLayout.TILE_HEIGHT_MASK
394: | ImageLayout.TILE_GRID_X_OFFSET_MASK | ImageLayout.TILE_GRID_Y_OFFSET_MASK)) == 0) {
395: layout.setTileGridXOffset(layout.getMinX(sourceImage));
396: layout.setTileGridYOffset(layout.getMinY(sourceImage));
397: final int width = layout.getWidth(sourceImage);
398: final int height = layout.getHeight(sourceImage);
399: if (layout.getTileWidth(sourceImage) > width)
400: layout.setTileWidth(width);
401: if (layout.getTileHeight(sourceImage) > height)
402: layout.setTileHeight(height);
403: }
404: targetHints.put(JAI.KEY_IMAGE_LAYOUT, layout);
405: // it is crucial to correctly manage the Hints to control the
406: // replacement of IndexColorModel. It is worth to point out that setting
407: // the JAI.KEY_REPLACE_INDEX_COLOR_MODEL hint to true is not enough to
408: // force the operators to do an expansion.
409: // If we explicitly provide an ImageLayout built with the source image
410: // where the CM and the SM are valid. those will be employed overriding
411: // a the possibility to expand the color model.
412: if (actionTaken != 1) {
413: targetHints
414: .add(ImageUtilities.DONT_REPLACE_INDEX_COLOR_MODEL);
415: } else {
416: targetHints.add(ImageUtilities.REPLACE_INDEX_COLOR_MODEL);
417: layout.unsetValid(ImageLayout.COLOR_MODEL_MASK);
418: layout.unsetValid(ImageLayout.SAMPLE_MODEL_MASK);
419: }
420: /*
421: * If the user requests a new grid geometry with the same coordinate reference system,
422: * and if the grid geometry is equivalents to a simple extraction of a sub-area, then
423: * delegates the work to a "Crop" operation.
424: */
425: String operation = null;
426: if (((allSteps instanceof AffineTransform) && XAffineTransform
427: .isIdentity((AffineTransform) allSteps, 10E-2))
428: || allSteps.isIdentity()) {
429: if (xmin > xminS && xmax < xmaxS && ymin > yminS
430: && ymax < ymaxS) {
431: operation = "Crop";
432: paramBlk = paramBlk.add((float) (xmin)).add(
433: (float) (ymin)).add((float) (xmax - xmin)).add(
434: (float) (ymax - ymin));
435: } else if (xmin == xminS && xmax == xmaxS && ymin == yminS
436: && ymax == ymaxS) {
437: // Optimization in case we have nothing to do, not even a crop.
438: GridCoverage2D targetCoverage = new Resampler2D(
439: sourceCoverage, sourceImage, targetGG,
440: actionTaken == 1 ? null : sourceCoverage
441: .getSampleDimensions());
442: switch (actionTaken) {
443: case 3:
444: targetCoverage = targetCoverage.geophysics(true);
445: break;
446: case 2:
447: targetCoverage = targetCoverage.geophysics(false);
448: break;
449: }
450: return targetCoverage;
451: }
452: }
453: /*
454: * Special case for the affine transform. Try to use the JAI "Affine" operation instead of
455: * the more general "Warp" one. JAI provides native acceleration for the affine operation.
456: * NOTE: "Affine", "Scale", "Translate", "Rotate" and similar operations ignore the 'xmin',
457: * 'ymin', 'width' and 'height' image layout. Consequently, we can't use this operation if
458: * the user provided explicitely a grid range.
459: *
460: * Note: if the user didn't specified any grid geometry, then a yet cheaper approach is to
461: * just update the 'gridToCRS' value. We returns a grid coverage wrapping the SOURCE
462: * image with the updated grid geometry.
463: */
464: double[] background = null;
465: if (operation == null) {
466: if (allSteps instanceof AffineTransform
467: && (automaticGG || automaticGR)) {
468: if (automaticGG) {
469: background = null;// we won't use it
470: // Cheapest approach: just update 'gridToCRS'.
471: GridCoverage2D targetCoverage;
472: MathTransform mtr;
473: mtr = sourceGG.getGridToCRS();
474: mtr = mtFactory.createConcatenatedTransform(mtr,
475: step2.inverse());
476: targetGG = new GridGeometry2D(sourceGG
477: .getGridRange(), mtr, targetCRS);
478: /*
479: * Note: do NOT use the "GridGeometry2D(sourceGridRange,
480: * targetEnvelope)" constructor in the above line. We must
481: * give a MathTransform argument to the constructor, not an
482: * Envelope, because the later infer a MathTransform using
483: * heuristic rules. Only the constructor with a
484: * MathTransform argument is fully accurate.
485: */
486: targetCoverage = new Resampler2D(sourceCoverage,
487: sourceImage, targetGG,
488: actionTaken == 1 ? null : sourceCoverage
489: .getSampleDimensions());
490: switch (actionTaken) {
491: case 3:
492: targetCoverage = targetCoverage
493: .geophysics(true);
494: break;
495: case 2:
496: targetCoverage = targetCoverage
497: .geophysics(false);
498: break;
499: }
500: return targetCoverage;
501: }
502: // More general approach: apply the affine transform.
503: if (automaticGR) {
504: operation = "Affine";
505: // prepare the values for the background
506: background = CoverageUtilities
507: .getBackgroundValues(sourceCoverage);
508: final AffineTransform affine = (AffineTransform) allSteps
509: .inverse();
510: paramBlk = paramBlk.add(affine).add(interpolation)
511: .add(background);
512: }
513: } else {
514: /*
515: * General case: construct the warp transform.
516: */
517: operation = "Warp";
518: final Warp warp = WarpTransform2D.getWarp(
519: sourceCoverage.getName(),
520: (MathTransform2D) allSteps2D);
521: // prepare the values for the background
522: background = CoverageUtilities
523: .getBackgroundValues(sourceCoverage);
524: paramBlk = paramBlk.add(warp).add(interpolation).add(
525: background);
526: }
527: }
528: final RenderedOp targetImage = processor.createNS(operation,
529: paramBlk, targetHints);
530: final Locale locale = sourceCoverage.getLocale(); // For logging purpose.
531: /*
532: * The JAI operation sometime returns an image with a bounding box different than what we
533: * expected. This is true especially for the "Affine" operation: the JAI documentation said
534: * explicitly that xmin, ymin, width and height image layout hints are ignored for this one.
535: * As a safety, we check the bounding box in any case. If it doesn't matches, then we will
536: * reconstruct the target grid geometry.
537: */
538: final int[] lower = targetGR.getLower().getCoordinateValues();
539: final int[] upper = targetGR.getUpper().getCoordinateValues();
540: lower[xAxis] = targetImage.getMinX();
541: lower[yAxis] = targetImage.getMinY();
542: upper[xAxis] = targetImage.getMaxX();
543: upper[yAxis] = targetImage.getMaxY();
544: final GridRange actualGR = new GeneralGridRange(lower, upper);
545: if (!targetGR.equals(actualGR)) {
546: MathTransform gridToCRS = targetGG.getGridToCRS();
547: targetGG = new GridGeometry2D(actualGR, gridToCRS,
548: targetCRS);
549: if (!automaticGR) {
550: log(Logging.getResources(locale).getLogRecord(
551: Level.WARNING,
552: LoggingKeys.ADJUSTED_GRID_GEOMETRY_$1,
553: sourceCoverage.getName().toString(locale)));
554: }
555: }
556: /*
557: * Constructs the final grid coverage, then log a message as in the following example:
558: *
559: * Resampled coverage "Foo" from coordinate system "myCS" (for an image of size
560: * 1000x1500) to coordinate system "WGS84" (image size 1000x1500). JAI operation
561: * is "Warp" with "Nearest" interpolation on geophysics pixels values. Background
562: * value is 255.
563: */
564: GridCoverage2D targetCoverage = new Resampler2D(sourceCoverage,
565: targetImage, targetGG, actionTaken == 1 ? null
566: : sourceCoverage.getSampleDimensions());
567: switch (actionTaken) {
568: case 3:
569: targetCoverage = targetCoverage.geophysics(true);
570: break;
571: case 2:
572: targetCoverage = targetCoverage.geophysics(false);
573: break;
574: }
575: assert CRS.equalsIgnoreMetadata(targetCoverage
576: .getCoordinateReferenceSystem(), targetCRS) : targetGG;
577: assert ((GridGeometry2D) targetCoverage.getGridGeometry())
578: .getGridRange2D().equals(targetImage.getBounds()) : targetGG;
579: if (AbstractProcessor.LOGGER.isLoggable(LOGGING_LEVEL)) {
580: log(Logging
581: .getResources(locale)
582: .getLogRecord(
583: LOGGING_LEVEL,
584: LoggingKeys.APPLIED_RESAMPLE_$11,
585: new Object[] {
586: /* {0} */sourceCoverage.getName()
587: .toString(locale),
588: /* {1} */sourceCoverage
589: .getCoordinateReferenceSystem()
590: .getName().getCode(),
591: /* {2} */new Integer(sourceImage
592: .getWidth()),
593: /* {3} */new Integer(sourceImage
594: .getHeight()),
595: /* {4} */targetCoverage
596: .getCoordinateReferenceSystem()
597: .getName().getCode(),
598: /* {5} */new Integer(targetImage
599: .getWidth()),
600: /* {6} */new Integer(targetImage
601: .getHeight()),
602: /* {7} */targetImage
603: .getOperationName(),
604: /* {8} */new Integer(
605: sourceCoverage == sourceCoverage
606: .geophysics(true) ? 1
607: : 0),
608: /* {9} */ImageUtilities
609: .getInterpolationName(interpolation),
610: /* {10} */(background != null) ? background.length == 1 ? (Double
611: .isNaN(background[0]) ? (Object) "NaN"
612: : (Object) new Double(
613: background[0]))
614: : (Object) XArray.toString(
615: background, locale)
616: : "No background used" }));
617: }
618: return targetCoverage;
619: }
620:
621: /**
622: * Returns the math transform for the two specified dimensions of the specified transform.
623: *
624: * @param transform The transform.
625: * @param mtFactory The factory to use for extracting the sub-transform.
626: * @param sourceGG The grid geometry which is the source of the <strong>transform</strong>.
627: * This is {@code targetGG} in the {@link #reproject} method, because the
628: * later computes a transform from target to source grid geometry.
629: * @return The {@link MathTransform2D} part of {@code transform}.
630: * @throws FactoryException If {@code transform} is not separable.
631: */
632: private static MathTransform2D getMathTransform2D(
633: final MathTransform transform,
634: final MathTransformFactory mtFactory,
635: final GridGeometry2D sourceGG) throws FactoryException {
636: final DimensionFilter filter = new DimensionFilter(mtFactory);
637: filter.addSourceDimension(sourceGG.axisDimensionX);
638: filter.addSourceDimension(sourceGG.axisDimensionY);
639: MathTransform candidate = filter.separate(transform);
640: if (candidate instanceof MathTransform2D) {
641: return (MathTransform2D) candidate;
642: }
643: filter.addTargetDimension(sourceGG.axisDimensionX);
644: filter.addTargetDimension(sourceGG.axisDimensionY);
645: candidate = filter.separate(transform);
646: if (candidate instanceof MathTransform2D) {
647: return (MathTransform2D) candidate;
648: }
649: throw new FactoryException(Errors
650: .format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
651: }
652:
653: /**
654: * Checks if two geometries are equal, ignoring unspecified fields. If one or both
655: * geometries has no "gridToCRS" transform, then this properties is not taken in account.
656: * Same apply for the grid range.
657: *
658: * @param range1 The first range.
659: * @param range2 The second range.
660: * @return {@code true} if the two geometries are equal, ignoring unspecified fields.
661: */
662: private static boolean equivalent(final GridGeometry2D geom1,
663: final GridGeometry2D geom2) {
664: if (geom1.equals(geom2)) {
665: return true;
666: }
667: if (geom1.isDefined(GridGeometry2D.GRID_RANGE)
668: && geom2.isDefined(GridGeometry2D.GRID_RANGE)) {
669: if (!geom1.getGridRange().equals(geom2.getGridRange())) {
670: return false;
671: }
672: }
673: if (geom1.isDefined(GridGeometry2D.GRID_TO_CRS)
674: && geom2.isDefined(GridGeometry2D.GRID_TO_CRS)) {
675: if (!geom1.getGridToCRS().equals(geom2.getGridToCRS())) {
676: return false;
677: }
678: }
679: return true;
680: }
681:
682: /**
683: * Cast the specified grid range into an envelope. This is used before to transform
684: * the envelope using {@link CTSUtilities#transform(MathTransform, Envelope)}.
685: */
686: private static Envelope toEnvelope(final GridRange gridRange) {
687: final int dimension = gridRange.getDimension();
688: final double[] lower = new double[dimension];
689: final double[] upper = new double[dimension];
690: for (int i = 0; i < dimension; i++) {
691: lower[i] = gridRange.getLower(i);
692: upper[i] = gridRange.getUpper(i);
693: }
694: return new GeneralEnvelope(lower, upper);
695: }
696:
697: /**
698: * Log a message.
699: */
700: static void log(final LogRecord record) {
701: record.setSourceClassName("Resample");
702: record.setSourceMethodName("doOperation");
703: AbstractProcessor.LOGGER.log(record);
704: }
705: }
|