0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * This package contains documentation from OpenGIS specifications.
0018: * OpenGIS consortium's work is fully acknowledged here.
0019: */
0020: package org.geotools.coverage.grid;
0021:
0022: import java.awt.Point;
0023: import java.awt.RenderingHints;
0024: import java.awt.geom.Point2D;
0025: import java.awt.geom.Rectangle2D;
0026: import java.awt.image.*; // Numerous imports here.
0027: import java.awt.image.renderable.ParameterBlock;
0028: import java.awt.image.renderable.RenderableImage;
0029: import java.io.IOException;
0030: import java.io.InvalidClassException;
0031: import java.io.InvalidObjectException;
0032: import java.io.ObjectInputStream;
0033: import java.io.ObjectOutputStream;
0034: import java.lang.reflect.Field;
0035: import java.util.Arrays;
0036: import java.util.Collection;
0037: import java.util.HashSet;
0038: import java.util.Iterator;
0039: import java.util.List;
0040: import java.util.Locale;
0041: import java.util.Map;
0042: import java.util.logging.Level;
0043: import java.util.logging.LogRecord;
0044: import javax.units.Unit;
0045: import javax.media.jai.ImageLayout;
0046: import javax.media.jai.Interpolation;
0047: import javax.media.jai.InterpolationNearest;
0048: import javax.media.jai.JAI;
0049: import javax.media.jai.LookupTableJAI;
0050: import javax.media.jai.NullOpImage;
0051: import javax.media.jai.PlanarImage;
0052: import javax.media.jai.RenderedImageAdapter;
0053: import javax.media.jai.RenderedOp;
0054: import javax.media.jai.operator.LookupDescriptor;
0055: import javax.media.jai.operator.PiecewiseDescriptor;
0056: import javax.media.jai.operator.RescaleDescriptor;
0057: import javax.media.jai.remote.SerializableRenderedImage;
0058: import javax.media.jai.util.CaselessStringKey; // For javadoc
0059:
0060: import org.opengis.coverage.CannotEvaluateException;
0061: import org.opengis.coverage.PointOutsideCoverageException;
0062: import org.opengis.coverage.SampleDimension;
0063: import org.opengis.coverage.grid.GridCoverage;
0064: import org.opengis.coverage.grid.GridGeometry;
0065: import org.opengis.coverage.grid.GridRange;
0066: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0067: import org.opengis.referencing.operation.MathTransform1D;
0068: import org.opengis.referencing.operation.TransformException;
0069: import org.opengis.geometry.DirectPosition;
0070: import org.opengis.geometry.Envelope;
0071: import org.opengis.geometry.MismatchedDimensionException;
0072:
0073: import org.geotools.coverage.Category;
0074: import org.geotools.coverage.GridSampleDimension;
0075: import org.geotools.coverage.AbstractCoverage;
0076: import org.geotools.coverage.processing.AbstractProcessor;
0077: import org.geotools.geometry.Envelope2D;
0078: import org.geotools.resources.XArray;
0079: import org.geotools.resources.coverage.CoverageUtilities;
0080: import org.geotools.resources.i18n.Errors;
0081: import org.geotools.resources.i18n.ErrorKeys;
0082: import org.geotools.resources.i18n.Logging;
0083: import org.geotools.resources.i18n.LoggingKeys;
0084: import org.geotools.resources.image.ImageUtilities;
0085: import org.geotools.util.NumberRange;
0086:
0087: /**
0088: * Basic access to grid data values backed by a two-dimensional
0089: * {@linkplain RenderedImage rendered image}. Each band in an image is represented as a
0090: * {@linkplain GridSampleDimension sample dimension}.
0091: * <p>
0092: * Grid coverages are usually two-dimensional. However, {@linkplain #getEnvelope their envelope}
0093: * may have more than two dimensions. For example, a remote sensing image may be valid only over
0094: * some time range (the time of satellite pass over the observed area). Envelopes for such grid
0095: * coverage can have three dimensions: the two usual ones (horizontal extent along <var>x</var>
0096: * and <var>y</var>), and a third one for start time and end time (time extent along <var>t</var>).
0097: * However, the {@linkplain GeneralGridRange grid range} for all extra-dimension <strong>must</strong>
0098: * have a {@linkplain GeneralGridRange#getLength size} not greater than 1. In other words, a
0099: * {@code GridCoverage2D} can be a slice in a 3 dimensional grid coverage. Each slice can have an
0100: * arbitrary width and height (like any two-dimensional images), but only 1 voxel depth (a "voxel"
0101: * is a three-dimensional pixel).
0102: * <p>
0103: * <strong>Serialization note:</strong><br>
0104: * Because it is serializable, {@code GridCoverage2D} can be included as method argument or as
0105: * return type in <cite>Remote Method Invocation</cite> (RMI). However, the pixel data are not
0106: * sent during serialization. Instead, the image data are transmitted "on-demand" using socket
0107: * communications. This mechanism is implemented using JAI {@link SerializableRenderedImage}
0108: * class. While serialization (usually on server side) should work on J2SE 1.4 and above,
0109: * deserialization (usually on client side) of {@code GridCoverage2D} instances requires J2SE 1.5.
0110: *
0111: * @since 2.1
0112: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/grid/GridCoverage2D.java $
0113: * @version $Id: GridCoverage2D.java 26655 2007-08-22 13:57:25Z desruisseaux $
0114: * @author Martin Desruisseaux
0115: */
0116: public class GridCoverage2D extends AbstractGridCoverage implements
0117: RenderedCoverage {
0118: /**
0119: * For compatibility during cross-version serialization.
0120: */
0121: private static final long serialVersionUID = 667472989475027853L;
0122:
0123: /**
0124: * Slight number for rounding errors in floating point comparaison.
0125: */
0126: private static final float EPS = 1E-5f;
0127:
0128: /**
0129: * {@code true} if we should apply a conservative policy for the "piecewise" operation.
0130: * The conservative policy is to apply "piecewise" only if there is no ambiguity about what
0131: * the user wants.
0132: */
0133: private static final boolean CONSERVATIVE_PIECEWISE = true;
0134:
0135: /**
0136: * A grid coverage using the sample dimensions {@code GridSampleDimension.inverse}.
0137: * This object is constructed and returned by {@link #geophysics}. Constructed when
0138: * first needed. May appears also in the {@link #sources} list.
0139: */
0140: private transient GridCoverage2D inverse;
0141:
0142: /**
0143: * The raster data.
0144: */
0145: protected transient final PlanarImage image;
0146:
0147: /**
0148: * The serialized image, as an instance of {@link SerializableRenderedImage}.
0149: * This image will be created only when first needed during serialization.
0150: */
0151: private RenderedImage serializedImage;
0152:
0153: /**
0154: * The grid geometry.
0155: */
0156: protected final GridGeometry2D gridGeometry;
0157:
0158: /**
0159: * List of sample dimension information for the grid coverage.
0160: * For a grid coverage, a sample dimension is a band. The sample dimension information
0161: * include such things as description, data type of the value (bit, byte, integer...),
0162: * the no data values, minimum and maximum values and a color table if one is associated
0163: * with the dimension. A coverage must have at least one sample dimension.
0164: */
0165: private final GridSampleDimension[] sampleDimensions;
0166:
0167: /**
0168: * {@code true} is all sample in the image are geophysics values.
0169: */
0170: private final boolean isGeophysics;
0171:
0172: /**
0173: * {@code true} if this coverage has been disposed.
0174: *
0175: * @see #dispose
0176: */
0177: private transient boolean disposed;
0178:
0179: /**
0180: * The preferred encoding to use for serialization using the {@code writeObject} method,
0181: * or {@code null} for the default encoding. This value is set by {@link GridCoverageFactory}
0182: * according the hints provided to the factory.
0183: */
0184: transient String tileEncoding;
0185:
0186: /**
0187: * Construct a new grid coverage with the same parameter than the specified
0188: * coverage. This constructor is useful when creating a coverage with
0189: * identical data, but in which some method has been overridden in order to
0190: * process data differently (e.g. interpolating them).
0191: *
0192: * @param name The name for this coverage, or {@code null} for the same than {@code coverage}.
0193: * @param coverage The source grid coverage.
0194: */
0195: protected GridCoverage2D(final CharSequence name,
0196: final GridCoverage2D coverage) {
0197: super (name, coverage);
0198: image = coverage.image;
0199: gridGeometry = coverage.gridGeometry;
0200: sampleDimensions = coverage.sampleDimensions;
0201: isGeophysics = coverage.isGeophysics;
0202: tileEncoding = coverage.tileEncoding;
0203: }
0204:
0205: /**
0206: * Constructs a grid coverage with the specified {@linkplain GridGeometry2D grid geometry} and
0207: * {@linkplain GridSampleDimension sample dimensions}. The {@linkplain Envelope envelope}
0208: * (including the {@linkplain CoordinateReferenceSystem coordinate reference system}) is
0209: * inferred from the grid geometry.
0210: * <p>
0211: * This constructor accepts an optional set of properties. "Properties" in <cite>Java Advanced
0212: * Imaging</cite> is what OpenGIS calls "Metadata". Keys are {@link String} objects
0213: * ({@link CaselessStringKey} are accepted as well), while values may be any {@link Object}.
0214: *
0215: * @param name The grid coverage name.
0216: * @param image The image.
0217: * @param gridGeometry The grid geometry (must contains an {@linkplain GridGeometry2D#getEnvelope
0218: * envelope} with its {@linkplain GridGeometry2D#getCoordinateReferenceSystem
0219: * coordinate reference system} and a "{@linkplain
0220: * GridGeometry2D#getGridToCoordinateSystem grid to CRS}" transform).
0221: * @param bands Sample dimensions for each image band, or {@code null} for default sample
0222: * dimensions. If non-null, then this array's length must matches the number
0223: * of bands in {@code image}.
0224: * @param sources The sources for this grid coverage, or {@code null} if none.
0225: * @param properties The set of properties for this coverage, or {@code null} none.
0226: *
0227: * @throws IllegalArgumentException if the number of bands differs from the number of sample
0228: * dimensions.
0229: *
0230: * @since 2.2
0231: */
0232: protected GridCoverage2D(final CharSequence name,
0233: final PlanarImage image, GridGeometry2D gridGeometry,
0234: final GridSampleDimension[] bands,
0235: final GridCoverage[] sources, final Map properties)
0236: throws IllegalArgumentException {
0237: super (name, gridGeometry.getCoordinateReferenceSystem(),
0238: sources, image, properties);
0239: this .image = image;
0240: /*
0241: * Wraps the user-suplied sample dimensions into instances of Grid2DSampleDimension. This
0242: * process will creates default sample dimensions if the user supplied null values. Those
0243: * default will be inferred from image type (integers, floats...) and range of values. If
0244: * an inconsistency is found in user-supplied sample dimensions, an IllegalArgumentException
0245: * is thrown.
0246: */
0247: sampleDimensions = new GridSampleDimension[image.getNumBands()];
0248: isGeophysics = Grid2DSampleDimension.create(name, image, bands,
0249: sampleDimensions);
0250: /*
0251: * Computes the grid range if it was not explicitly provided. The range will be inferred
0252: * from the image size, if needed. The envelope computation (if needed) requires a valid
0253: * 'gridToCRS' transform in the GridGeometry object. In any case, the envelope must be
0254: * non-empty and its dimension must matches the coordinate reference system's dimension.
0255: */
0256: final int dimension = crs.getCoordinateSystem().getDimension();
0257: if (!gridGeometry.isDefined(GridGeometry2D.GRID_RANGE)) {
0258: final GridRange r = new GeneralGridRange(image, dimension);
0259: if (gridGeometry.isDefined(GridGeometry2D.GRID_TO_CRS)) {
0260: gridGeometry = new GridGeometry2D(r, gridGeometry
0261: .getGridToCRS(), crs);
0262: } else {
0263: /*
0264: * If the math transform was not explicitly specified by the user, then it will be
0265: * computed from the envelope. In this case, some heuristic rules are used in order
0266: * to decide if we should reverse some axis directions or swap axis.
0267: */
0268: gridGeometry = new GridGeometry2D(r, gridGeometry
0269: .getEnvelope());
0270: }
0271: } else {
0272: /*
0273: * Makes sure that the 'gridToCRS' transform is defined.
0274: * An exception will be thrown otherwise.
0275: */
0276: gridGeometry.getGridToCRS();
0277: }
0278: this .gridGeometry = gridGeometry;
0279: assert gridGeometry.isDefined(GridGeometry2D.CRS
0280: | GridGeometry2D.ENVELOPE | GridGeometry2D.GRID_RANGE
0281: | GridGeometry2D.GRID_TO_CRS);
0282: /*
0283: * Last argument checks. The image size must be consistent with the grid range
0284: * and the envelope must be non-empty.
0285: */
0286: final String error = checkConsistency(image, gridGeometry);
0287: if (error != null) {
0288: throw new IllegalArgumentException(error);
0289: }
0290: if (dimension <= Math.max(gridGeometry.axisDimensionX,
0291: gridGeometry.axisDimensionY)
0292: || !(gridGeometry.envelope
0293: .getLength(gridGeometry.axisDimensionX) > 0)
0294: || !(gridGeometry.envelope
0295: .getLength(gridGeometry.axisDimensionY) > 0)) {
0296: throw new IllegalArgumentException(Errors
0297: .format(ErrorKeys.EMPTY_ENVELOPE));
0298: }
0299: }
0300:
0301: /**
0302: * Checks if the bounding box of the specified image is consistents with the specified
0303: * grid geometry. If an inconsistency has been found, then an error string is returned.
0304: * This string will be typically used as a message in an exception to be thrown.
0305: * <p>
0306: * Note that a succesful check at construction time may fails later if the image is part
0307: * of a JAI chain (i.e. is a {@link RenderedOp}) and its bounds has been edited (i.e the
0308: * image node as been re-rendered). Since {@code GridCoverage} are immutable by design,
0309: * we are not allowed to propagate the image change here. The {@link #getGridGeometry} method
0310: * will thrown an {@link IllegalStateException} in this case.
0311: */
0312: private static String checkConsistency(final RenderedImage image,
0313: final GridGeometry2D grid) {
0314: final GridRange range = grid.getGridRange();
0315: final int dimension = range.getDimension();
0316: for (int i = 0; i < dimension; i++) {
0317: final int min, length;
0318: final Object label;
0319: if (i == grid.gridDimensionX) {
0320: min = image.getMinX();
0321: length = image.getWidth();
0322: label = "\"X\"";
0323: } else if (i == grid.gridDimensionY) {
0324: min = image.getMinY();
0325: length = image.getHeight();
0326: label = "\"Y\"";
0327: } else {
0328: min = range.getLower(i);
0329: length = Math.min(Math.max(range.getUpper(i), 0), 1);
0330: label = new Integer(i);
0331: }
0332: if (range.getLower(i) != min
0333: || range.getLength(i) != length) {
0334: return Errors.format(ErrorKeys.BAD_GRID_RANGE_$3,
0335: label, new Integer(min), new Integer(min
0336: + length));
0337: }
0338: }
0339: return null;
0340: }
0341:
0342: /**
0343: * Returns {@code true} if grid data can be edited. The default
0344: * implementation returns {@code true} if {@link #image} is an
0345: * instance of {@link WritableRenderedImage}.
0346: */
0347: public boolean isDataEditable() {
0348: return (image instanceof WritableRenderedImage);
0349: }
0350:
0351: /**
0352: * Returns information for the grid coverage geometry. Grid geometry
0353: * includes the valid range of grid coordinates and the georeferencing.
0354: *
0355: * @todo Use covariant return type once we are allowed to compile for J2SE 1.5.
0356: */
0357: public GridGeometry getGridGeometry() {
0358: final String error = checkConsistency(image, gridGeometry);
0359: if (error != null) {
0360: throw new IllegalStateException(error);
0361: }
0362: return gridGeometry;
0363: }
0364:
0365: /**
0366: * Returns the bounding box for the coverage domain in coordinate reference system coordinates.
0367: * The returned envelope have at least two dimensions. It may have more dimensions if the
0368: * coverage has some extent in other dimensions (for example a depth, or a start and end time).
0369: */
0370: public Envelope getEnvelope() {
0371: return gridGeometry.getEnvelope();
0372: }
0373:
0374: /**
0375: * Returns the two-dimensional bounding box for the coverage domain in coordinate reference
0376: * system coordinates. If the coverage envelope has more than two dimensions, only the
0377: * dimensions used in the underlying rendered image are returned.
0378: */
0379: public Envelope2D getEnvelope2D() {
0380: return gridGeometry.getEnvelope2D();
0381: }
0382:
0383: /**
0384: * Returns the two-dimensional part of this grid coverage CRS. This is usually (but not
0385: * always) identical to the {@linkplain #getCoordinateReferenceSystem full CRS}.
0386: *
0387: * @see #getCoordinateReferenceSystem
0388: */
0389: public CoordinateReferenceSystem getCoordinateReferenceSystem2D() {
0390: return gridGeometry.getCoordinateReferenceSystem2D();
0391: }
0392:
0393: /**
0394: * Returns the number of bands in the grid coverage.
0395: */
0396: public int getNumSampleDimensions() {
0397: return sampleDimensions.length;
0398: }
0399:
0400: /**
0401: * Retrieve sample dimension information for the coverage.
0402: * For a grid coverage, a sample dimension is a band. The sample dimension information
0403: * include such things as description, data type of the value (bit, byte, integer...),
0404: * the no data values, minimum and maximum values and a color table if one is associated
0405: * with the dimension. A coverage must have at least one sample dimension.
0406: */
0407: public SampleDimension getSampleDimension(final int index) {
0408: return sampleDimensions[index];
0409: }
0410:
0411: /**
0412: * Returns all sample dimensions for this grid coverage.
0413: */
0414: public GridSampleDimension[] getSampleDimensions() {
0415: return (GridSampleDimension[]) sampleDimensions.clone();
0416: }
0417:
0418: /**
0419: * Returns the interpolation used for all {@code evaluate(...)} methods.
0420: * The default implementation returns {@link InterpolationNearest}.
0421: *
0422: * @return The interpolation.
0423: */
0424: public Interpolation getInterpolation() {
0425: return Interpolation.getInstance(Interpolation.INTERP_NEAREST);
0426: }
0427:
0428: /**
0429: * Returns the value vector for a given point in the coverage.
0430: * A value for each sample dimension is included in the vector.
0431: */
0432: public Object evaluate(final DirectPosition point)
0433: throws CannotEvaluateException {
0434: final int dataType = image.getSampleModel().getDataType();
0435: switch (dataType) {
0436: case DataBuffer.TYPE_BYTE:
0437: return evaluate(point, (byte[]) null);
0438: case DataBuffer.TYPE_SHORT: // Fall through
0439: case DataBuffer.TYPE_USHORT: // Fall through
0440: case DataBuffer.TYPE_INT:
0441: return evaluate(point, (int[]) null);
0442: case DataBuffer.TYPE_FLOAT:
0443: return evaluate(point, (float[]) null);
0444: case DataBuffer.TYPE_DOUBLE:
0445: return evaluate(point, (double[]) null);
0446: default:
0447: throw new CannotEvaluateException();
0448: }
0449: }
0450:
0451: /**
0452: * Returns a sequence of byte values for a given point in the coverage.
0453: *
0454: * @param coord The coordinate point where to evaluate.
0455: * @param dest An array in which to store values, or {@code null}.
0456: * @return An array containing values.
0457: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0458: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0459: * failed because the input point has invalid coordinates.
0460: */
0461: public byte[] evaluate(final DirectPosition coord, byte[] dest)
0462: throws CannotEvaluateException {
0463: final int[] array = evaluate(coord, (int[]) null);
0464: if (dest == null) {
0465: dest = new byte[array.length];
0466: }
0467: for (int i = 0; i < array.length; i++) {
0468: dest[i] = (byte) array[i];
0469: }
0470: return dest;
0471: }
0472:
0473: /**
0474: * Returns a sequence of integer values for a given point in the coverage.
0475: *
0476: * @param coord The coordinate point where to evaluate.
0477: * @param dest An array in which to store values, or {@code null}.
0478: * @return An array containing values.
0479: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0480: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0481: * failed because the input point has invalid coordinates.
0482: */
0483: public int[] evaluate(final DirectPosition coord, final int[] dest)
0484: throws CannotEvaluateException {
0485: return evaluate(toPoint2D(coord), dest);
0486: }
0487:
0488: /**
0489: * Returns a sequence of float values for a given point in the coverage.
0490: *
0491: * @param coord The coordinate point where to evaluate.
0492: * @param dest An array in which to store values, or {@code null}.
0493: * @return An array containing values.
0494: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0495: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0496: * failed because the input point has invalid coordinates.
0497: */
0498: public float[] evaluate(final DirectPosition coord,
0499: final float[] dest) throws CannotEvaluateException {
0500: return evaluate(toPoint2D(coord), dest);
0501: }
0502:
0503: /**
0504: * Returns a sequence of double values for a given point in the coverage.
0505: *
0506: * @param coord The coordinate point where to evaluate.
0507: * @param dest An array in which to store values, or {@code null}.
0508: * @return An array containing values.
0509: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0510: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0511: * failed because the input point has invalid coordinates.
0512: */
0513: public double[] evaluate(final DirectPosition coord,
0514: final double[] dest) throws CannotEvaluateException {
0515: return evaluate(toPoint2D(coord), dest);
0516: }
0517:
0518: /**
0519: * Converts the specified point into a two-dimensional one.
0520: *
0521: * @param point The point to transform into a {@link Point2D} object.
0522: * @return The specified point as a {@link Point2D} object.
0523: * @throws MismatchedDimensionException if the point doesn't have the expected dimension.
0524: */
0525: private Point2D toPoint2D(final DirectPosition point)
0526: throws MismatchedDimensionException {
0527: final int actual = point.getDimension();
0528: final int expected = crs.getCoordinateSystem().getDimension();
0529: if (actual != expected) {
0530: throw new MismatchedDimensionException(Errors.format(
0531: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
0532: actual), new Integer(expected)));
0533: }
0534: if (point instanceof Point2D) {
0535: return (Point2D) point;
0536: }
0537: return new Point2D.Double(point
0538: .getOrdinate(gridGeometry.axisDimensionX), point
0539: .getOrdinate(gridGeometry.axisDimensionY));
0540: }
0541:
0542: /**
0543: * Returns a sequence of integer values for a given two-dimensional point in the coverage.
0544: *
0545: * @param coord The coordinate point where to evaluate.
0546: * @param dest An array in which to store values, or {@code null}.
0547: * @return An array containing values.
0548: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0549: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0550: * failed because the input point has invalid coordinates.
0551: */
0552: public int[] evaluate(final Point2D coord, final int[] dest)
0553: throws CannotEvaluateException {
0554: final Point2D pixel = gridGeometry.inverseTransform(coord);
0555: final double fx = pixel.getX();
0556: final double fy = pixel.getY();
0557: if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
0558: final int x = (int) Math.round(fx);
0559: final int y = (int) Math.round(fy);
0560: if (image.getBounds().contains(x, y)) { // getBounds() returns a cached instance.
0561: return image.getTile(image.XToTileX(x),
0562: image.YToTileY(y)).getPixel(x, y, dest);
0563: }
0564: }
0565: throw new PointOutsideCoverageException(
0566: pointOutsideCoverage(coord));
0567: }
0568:
0569: /**
0570: * Returns a sequence of float values for a given two-dimensional point in the coverage.
0571: *
0572: * @param coord The coordinate point where to evaluate.
0573: * @param dest An array in which to store values, or {@code null}.
0574: * @return An array containing values.
0575: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0576: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0577: * failed because the input point has invalid coordinates.
0578: */
0579: public float[] evaluate(final Point2D coord, final float[] dest)
0580: throws CannotEvaluateException {
0581: final Point2D pixel = gridGeometry.inverseTransform(coord);
0582: final double fx = pixel.getX();
0583: final double fy = pixel.getY();
0584: if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
0585: final int x = (int) Math.round(fx);
0586: final int y = (int) Math.round(fy);
0587: if (image.getBounds().contains(x, y)) { // getBounds() returns a cached instance.
0588: return image.getTile(image.XToTileX(x),
0589: image.YToTileY(y)).getPixel(x, y, dest);
0590: }
0591: }
0592: throw new PointOutsideCoverageException(
0593: pointOutsideCoverage(coord));
0594: }
0595:
0596: /**
0597: * Returns a sequence of double values for a given two-dimensional point in the coverage.
0598: *
0599: * @param coord The coordinate point where to evaluate.
0600: * @param dest An array in which to store values, or {@code null}.
0601: * @return An array containing values.
0602: * @throws CannotEvaluateException if the values can't be computed at the specified coordinate.
0603: * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation
0604: * failed because the input point has invalid coordinates.
0605: */
0606: public double[] evaluate(final Point2D coord, final double[] dest)
0607: throws CannotEvaluateException {
0608: final Point2D pixel = gridGeometry.inverseTransform(coord);
0609: final double fx = pixel.getX();
0610: final double fy = pixel.getY();
0611: if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
0612: final int x = (int) Math.round(fx);
0613: final int y = (int) Math.round(fy);
0614: if (image.getBounds().contains(x, y)) { // getBounds() returns a cached instance.
0615: return image.getTile(image.XToTileX(x),
0616: image.YToTileY(y)).getPixel(x, y, dest);
0617: }
0618: }
0619: throw new PointOutsideCoverageException(
0620: pointOutsideCoverage(coord));
0621: }
0622:
0623: /**
0624: * Returns a debug string for the specified coordinate. This method produces a
0625: * string with pixel coordinates and pixel values for all bands (with geophysics
0626: * values or category name in parenthesis). Example for a 1-banded image:
0627: *
0628: * <blockquote><pre>(1171,1566)=[196 (29.6 °C)]</pre></blockquote>
0629: *
0630: * @param coord The coordinate point where to evaluate.
0631: * @return A string with pixel coordinates and pixel values at the specified location,
0632: * or {@code null} if {@code coord} is outside coverage.
0633: */
0634: public synchronized String getDebugString(final DirectPosition coord) {
0635: Point2D pixel = toPoint2D(coord);
0636: pixel = gridGeometry.inverseTransform(pixel);
0637: final int x = (int) Math.round(pixel.getX());
0638: final int y = (int) Math.round(pixel.getY());
0639: if (image.getBounds().contains(x, y)) { // getBounds() returns a cached instance.
0640: final int numBands = image.getNumBands();
0641: final Raster raster = image.getTile(image.XToTileX(x),
0642: image.YToTileY(y));
0643: final int datatype = image.getSampleModel().getDataType();
0644: final StringBuffer buffer = new StringBuffer();
0645: buffer.append('(');
0646: buffer.append(x);
0647: buffer.append(',');
0648: buffer.append(y);
0649: buffer.append(")=[");
0650: for (int band = 0; band < numBands; band++) {
0651: if (band != 0) {
0652: buffer.append(";\u00A0");
0653: }
0654: final double sample = raster
0655: .getSampleDouble(x, y, band);
0656: switch (datatype) {
0657: case DataBuffer.TYPE_DOUBLE:
0658: buffer.append((double) sample);
0659: break;
0660: case DataBuffer.TYPE_FLOAT:
0661: buffer.append((float) sample);
0662: break;
0663: default:
0664: buffer.append((int) sample);
0665: break;
0666: }
0667: final String formatted = sampleDimensions[band]
0668: .getLabel(sample, null);
0669: if (formatted != null) {
0670: buffer.append("\u00A0(");
0671: buffer.append(formatted);
0672: buffer.append(')');
0673: }
0674: }
0675: buffer.append(']');
0676: return buffer.toString();
0677: }
0678: return null;
0679: }
0680:
0681: /**
0682: * Returns the optimal size to use for each dimension when accessing grid values.
0683: * The default implementation returns the image's tiles size.
0684: */
0685: public int[] getOptimalDataBlockSizes() {
0686: final int[] size = new int[getDimension()];
0687: Arrays.fill(size, 1);
0688: size[gridGeometry.gridDimensionX] = image.getTileWidth();
0689: size[gridGeometry.gridDimensionY] = image.getTileHeight();
0690: return size;
0691: }
0692:
0693: /**
0694: * Returns grid data as a rendered image.
0695: */
0696: public RenderedImage getRenderedImage() {
0697: ensureValid();
0698: return image;
0699: }
0700:
0701: /**
0702: * Returns 2D view of this grid coverage as a renderable image.
0703: * This method allows interoperability with Java2D.
0704: *
0705: * @param xAxis Dimension to use for <var>x</var> axis.
0706: * @param yAxis Dimension to use for <var>y</var> axis.
0707: * @return A 2D view of this grid coverage as a renderable image.
0708: */
0709: public RenderableImage getRenderableImage(final int xAxis,
0710: final int yAxis) {
0711: ensureValid();
0712: if (xAxis == gridGeometry.axisDimensionX
0713: && yAxis == gridGeometry.axisDimensionY) {
0714: return new Renderable();
0715: } else {
0716: return super .getRenderableImage(xAxis, yAxis);
0717: }
0718: }
0719:
0720: /**
0721: * {inheritDoc}
0722: */
0723: //@Override
0724: public void show(String title, final int xAxis, final int yAxis) {
0725: ensureValid();
0726: final GridCoverage2D displayable = geophysics(false);
0727: if (displayable != this ) {
0728: displayable.show(title, xAxis, yAxis);
0729: return;
0730: }
0731: if (title == null || (title = title.trim()).length() == 0) {
0732: final StringBuffer buffer = new StringBuffer(String
0733: .valueOf(getName()));
0734: final int visibleBandIndex = CoverageUtilities
0735: .getVisibleBand(this );
0736: final SampleDimension visibleBand = getSampleDimension(visibleBandIndex);
0737: final Unit unit = visibleBand.getUnits();
0738: buffer.append(" - ").append(
0739: String.valueOf(visibleBand.getDescription()));
0740: if (unit != null) {
0741: buffer.append(" (").append(unit).append(')');
0742: }
0743: title = buffer.toString();
0744: }
0745: super .show(title, xAxis, yAxis);
0746: }
0747:
0748: /**
0749: * {inheritDoc}
0750: */
0751: //@Override
0752: public void show(final String title) {
0753: show(title, gridGeometry.axisDimensionX,
0754: gridGeometry.axisDimensionY);
0755: }
0756:
0757: /**
0758: * A view of a {@linkplain GridCoverage2D grid coverage} as a renderable image. Renderable images
0759: * allow interoperability with <A HREF="http://java.sun.com/products/java-media/2D/">Java2D</A>
0760: * for a two-dimensional slice of a grid coverage.
0761: *
0762: * @version $Id: GridCoverage2D.java 26655 2007-08-22 13:57:25Z desruisseaux $
0763: * @author Martin Desruisseaux
0764: *
0765: * @see AbstractCoverage#getRenderableImage
0766: *
0767: * @todo Override {@link #createRendering} and use the affine transform operation.
0768: * Also uses the JAI's "Transpose" operation is x and y axis are interchanged.
0769: */
0770: protected class Renderable extends AbstractCoverage.Renderable {
0771: /**
0772: * Constructs a renderable image.
0773: */
0774: public Renderable() {
0775: super (gridGeometry.axisDimensionX,
0776: gridGeometry.axisDimensionY);
0777: }
0778:
0779: /**
0780: * Returns a rendered image with a default width and height in pixels.
0781: *
0782: * @return A rendered image containing the rendered data
0783: */
0784: public RenderedImage createDefaultRendering() {
0785: if (xAxis == gridGeometry.axisDimensionX
0786: && yAxis == gridGeometry.axisDimensionY) {
0787: return getRenderedImage();
0788: }
0789: return super .createDefaultRendering();
0790: }
0791: }
0792:
0793: /**
0794: * Hints that the given area may be needed in the near future. Some implementations
0795: * may spawn a thread or threads to compute the tiles while others may ignore the hint.
0796: *
0797: * @param area A rectangle indicating which geographic area to prefetch.
0798: * This area's coordinates must be expressed according the
0799: * grid coverage's coordinate reference system, as given by
0800: * {@link #getCoordinateReferenceSystem}.
0801: */
0802: public void prefetch(final Rectangle2D area) {
0803: final Point[] tileIndices = image.getTileIndices(gridGeometry
0804: .inverseTransform(area));
0805: if (tileIndices != null) {
0806: image.prefetchTiles(tileIndices);
0807: }
0808: }
0809:
0810: /**
0811: * If {@code true}, returns the geophysics companion of this grid coverage. In a
0812: * <cite>geophysics grid coverage</cite>, all sample values are equals to geophysics
0813: * ("real world") values without the need for any transformation. In such geophysics
0814: * coverage, the {@linkplain SampleDimension#getSampleToGeophysics sample to geophysics}
0815: * transform is the identity transform for all sample dimensions. "No data" values are
0816: * expressed by {@linkplain Float#NaN NaN} numbers.
0817: * <p>
0818: * This method may be understood as applying the JAI's {@linkplain PiecewiseDescriptor
0819: * piecewise} operation with breakpoints specified by the {@link Category} objects in
0820: * each sample dimension. However, it is more general in that the transformation specified
0821: * with each breakpoint doesn't need to be linear. On an implementation note, this method
0822: * will really try to use the first of the following operations which is found applicable:
0823: * <cite>identity</cite>, {@linkplain LookupDescriptor lookup}, {@linkplain RescaleDescriptor
0824: * rescale}, {@linkplain PiecewiseDescriptor piecewise} and in last ressort a more general
0825: * (but slower) <cite>sample transcoding</cite> algorithm.
0826: * <p>
0827: * {@code GridCoverage} objects live by pair: a <cite>geophysics</cite> one (used for
0828: * computation) and a <cite>non-geophysics</cite> one (used for packing data, usually as
0829: * integers). The {@code geo} argument specifies which object from the pair is wanted,
0830: * regardless if this method is invoked on the geophysics or non-geophysics instance of the
0831: * pair. In other words, the result of {@code geophysics(b1).geophysics(b2).geophysics(b3)}
0832: * depends only on the value in the last call ({@code b3}).
0833: *
0834: * @param geo {@code true} to get a grid coverage with sample values equals to geophysics
0835: * values, or {@code false} to get the packed version.
0836: * @return The grid coverage. Never {@code null}, but may be {@code this}.
0837: *
0838: * @see GridSampleDimension#geophysics
0839: * @see Category#geophysics
0840: * @see LookupDescriptor
0841: * @see RescaleDescriptor
0842: * @see PiecewiseDescriptor
0843: */
0844: public GridCoverage2D geophysics(final boolean geo) {
0845: if (geo == isGeophysics) {
0846: return this ;
0847: }
0848: if (inverse != null) {
0849: return inverse;
0850: }
0851: if (!CoverageUtilities.hasTransform(sampleDimensions)) {
0852: return inverse = this ;
0853: }
0854: synchronized (this ) {
0855: inverse = createGeophysics(geo);
0856: if (inverse.inverse == null) {
0857: inverse.inverse = this ;
0858: } else if (inverse.inverse != this ) {
0859: final Locale locale = getLocale();
0860: throw new RasterFormatException(Errors.getResources(
0861: locale).getString(
0862: ErrorKeys.COVERAGE_ALREADY_BOUND_$2,
0863: "geophysics",
0864: inverse.inverse.getName().toString(locale)));
0865: }
0866: return inverse;
0867: }
0868: }
0869:
0870: /**
0871: * Invoked by {@link #geophysics(boolean)} when the packed or geophysics companion of this
0872: * grid coverage need to be created. Subclasses may override this method in order to modify
0873: * the object to be created.
0874: *
0875: * @param geo {@code true} to get a grid coverage with sample values equals to
0876: * geophysics values, or {@code false} to get the packed version.
0877: * @return The newly created grid coverage.
0878: *
0879: * @todo IndexColorModel seems to badly choose its sample model. As of JDK 1.4-rc1, it
0880: * construct a ComponentSampleModel, which is drawn very slowly to the screen. A
0881: * much faster sample model is PixelInterleavedSampleModel, which is the sample
0882: * model used by BufferedImage for TYPE_BYTE_INDEXED. We should check if this is
0883: * fixed in future J2SE release.
0884: *
0885: * @todo The "Piecewise" operation is disabled because javac 1.4.1_01 generate illegal
0886: * bytecode. This bug is fixed in javac 1.4.2-beta. However, we still have an
0887: * ArrayIndexOutOfBoundsException in JAI code...
0888: *
0889: * @todo A special case (exactly one linear relationship with one NaN value mapping
0890: * exactly to the index value 0) was optimized to the "Rescale" operation in
0891: * previous version. This case is very common, which make this optimization a
0892: * usefull one. Unfortunatly, it had to be disabled because there is nothing
0893: * in the "Rescale" preventing some real number (not NaN) to maps to 0 through
0894: * the normal linear relationship. Note that the optimization worked well in
0895: * previous version except for the above-cited problem. We can very easily re-
0896: * enable it later if we know the range of values really stored in the image
0897: * (as of JAI's "extrema" operation). If would suffice to add a check making
0898: * sure that the range of transformed values doesn't contains 0.
0899: */
0900: protected GridCoverage2D createGeophysics(final boolean geo) {
0901: ensureValid();
0902: /*
0903: * STEP 1 - Gets the source image and prepare the target sample dimensions.
0904: * As a slight optimisation, we skip the "Null" operations since
0905: * such image may be the result of some "Colormap" operation.
0906: */
0907: PlanarImage image = this .image;
0908: while (image instanceof NullOpImage) {
0909: final NullOpImage op = (NullOpImage) image;
0910: if (op.getNumSources() != 1) {
0911: break;
0912: }
0913: image = op.getSourceImage(0);
0914: }
0915: final int numBands = image.getNumBands();
0916: final int visibleBand = CoverageUtilities.getVisibleBand(image);
0917: final GridSampleDimension[] targetBands = (GridSampleDimension[]) sampleDimensions
0918: .clone();
0919: assert targetBands.length == numBands : targetBands.length;
0920: for (int i = 0; i < targetBands.length; i++) {
0921: targetBands[i] = targetBands[i].geophysics(geo);
0922: }
0923: /*
0924: * STEP 2 - Computes the layout for the destination RenderedImage. We will use the same
0925: * layout than the parent image, except for tile size if the parent image had
0926: * only one big tile, and for the color model and sample model (since we are
0927: * reformating data in the process of this operation).
0928: */
0929: ImageLayout layout = ImageUtilities.getImageLayout(image);
0930: ColorModel colors = targetBands[visibleBand].getColorModel(
0931: visibleBand, numBands);
0932: SampleModel model = colors.createCompatibleSampleModel(layout
0933: .getTileWidth(image), layout.getTileHeight(image));
0934: if (colors instanceof IndexColorModel
0935: && model.getClass().equals(ComponentSampleModel.class)) {
0936: // There is the 'IndexColorModel' hack (see method description).
0937: final int w = model.getWidth();
0938: final int h = model.getHeight();
0939: model = new PixelInterleavedSampleModel(colors
0940: .getTransferType(), w, h, 1, w, new int[1]);
0941: }
0942: layout = layout.setSampleModel(model).setColorModel(colors);
0943: ParameterBlock param = new ParameterBlock().addSource(image);
0944: RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,
0945: layout);
0946: hints.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
0947: String operation = null; // Will be set in step 3 or 4.
0948: /*
0949: * STEP 3 - Checks if the transcoding could be done with the JAI's "Lookup" operation.
0950: * This is probably the fatest operation available for 'geophysics(true)'.
0951: */
0952: try {
0953: final int sourceType = image.getSampleModel().getDataType();
0954: final int targetType = model.getDataType();
0955: final MathTransform1D[] transforms = new MathTransform1D[numBands];
0956: for (int i = 0; i < numBands; i++) {
0957: transforms[i] = sampleDimensions[i].geophysics(false)
0958: .getSampleToGeophysics();
0959: if (transforms[i] != null && !geo) {
0960: // We are going to convert geophysics values to packed one.
0961: transforms[i] = (MathTransform1D) transforms[i]
0962: .inverse();
0963: }
0964: }
0965: LookupTableJAI table = LookupTableFactory.create(
0966: sourceType, targetType, transforms);
0967: if (table != null) {
0968: operation = "Lookup";
0969: param = param.add(table);
0970: }
0971: } catch (TransformException exception) {
0972: // A value can't be transformed. Fallback on a more general operation.
0973: // REVISIT: the more general operations are likely to fail too...
0974: }
0975: /*
0976: * STEP 4 - Check if the transcoding could be done with a JAI's "Rescale" or "Piecewise"
0977: * operations. The "Rescale" operation requires a completly linear relationship
0978: * between the source and the destination sample values. The "Piecewise" operation
0979: * is less strict: piecewise breakpoints are very similar to categories, but the
0980: * transformation for all categories still have to be linear.
0981: */
0982: if (operation == null)
0983: try {
0984: boolean canRescale = true; // 'true' if the "Rescale" operation can be applied.
0985: boolean canPiecewise = true; // 'true' if the "Piecewise" operation can be applied.
0986: double[] scales = null; // The first argument for "Rescale".
0987: double[] offsets = null; // The second argument for "Rescale".
0988: float[][][] breakpoints = null; // The only argument for "Piecewise".
0989: testLinear: for (int i = 0; i < numBands; i++) {
0990: final GridSampleDimension sd = sampleDimensions[i];
0991: final List categories = sd.getCategories();
0992: final int numCategories = categories.size();
0993: float[] sourceBreakpoints = null;
0994: float[] targetBreakpoints = null;
0995: double expectedSource = Double.NaN;
0996: double expectedTarget = Double.NaN;
0997: int jbp = 0; // Break point index (vary with j)
0998: for (int j = 0; j < numCategories; j++) {
0999: final Category category = (Category) categories
1000: .get(j);
1001: MathTransform1D transform = category
1002: .geophysics(false)
1003: .getSampleToGeophysics();
1004: if (transform == null) {
1005: // A "qualitative" category was found. Those categories maps NaN values,
1006: // which need the special processing by our "SampleTranscode" operation.
1007: canPiecewise = false;
1008: if (false) {
1009: // As a special case, the "Rescale" operation could continue to work
1010: // if the NaN value maps to 0. Unfortunatly, this optimization had to
1011: // be disabled for now for the reason explained in the @todo tag in
1012: // method's comments.
1013: if (category.geophysics(geo).getRange()
1014: .getMinimum(true) == 0) {
1015: assert Double.isNaN(category
1016: .getRange().getMinimum()) : category;
1017: continue;
1018: }
1019: }
1020: canRescale = false;
1021: break testLinear;
1022: }
1023: if (!geo) {
1024: // We are going to convert geophysics values to packed one.
1025: transform = (MathTransform1D) transform
1026: .inverse();
1027: }
1028: final double offset = transform.transform(0);
1029: final double scale = transform
1030: .derivative(Double.NaN);
1031: if (Double.isNaN(scale) || Double.isNaN(offset)) {
1032: // One category doesn't use a linear transformation. We can't deal with
1033: // that with "Rescale" or "Piecewise". Fallback on our "SampleTranscode".
1034: canRescale = false;
1035: canPiecewise = false;
1036: break testLinear;
1037: }
1038: // Allocates arrays the first time the loop is run up to this point.
1039: // Store scale and offset, and check if they still the same.
1040: if (j == 0) {
1041: if (i == 0) {
1042: scales = new double[numBands];
1043: offsets = new double[numBands];
1044: breakpoints = new float[numBands][][];
1045: }
1046: sourceBreakpoints = new float[numCategories * 2];
1047: targetBreakpoints = new float[numCategories * 2];
1048: breakpoints[i] = new float[][] {
1049: sourceBreakpoints,
1050: targetBreakpoints };
1051: offsets[i] = offset;
1052: scales[i] = scale;
1053: }
1054: if (offset != offsets[i] || scale != scales[i]) {
1055: canRescale = false;
1056: }
1057: // Compute breakpoints.
1058: final NumberRange range = category.getRange();
1059: final double minimum = range.getMinimum(true);
1060: final double maximum = range.getMaximum(true);
1061: final float sourceMin = (float) minimum;
1062: final float sourceMax = (float) maximum;
1063: final float targetMin = (float) (minimum
1064: * scale + offset);
1065: final float targetMax = (float) (maximum
1066: * scale + offset);
1067: assert sourceMin <= sourceMax : range;
1068: if (Math.abs(minimum - expectedSource) <= EPS) {
1069: if (Math.abs(targetMin - expectedTarget) <= EPS) {
1070: // This breakpoint is identical to the previous one. Do not
1071: // duplicate; overwrites the previous one since this one is
1072: // likely to be more accurate.
1073: jbp--;
1074: } else {
1075: // Found a discontinuity!!! The "piecewise" operation is not really
1076: // designed for such case. The behavior between the last breakpoint
1077: // and the current one may not be what the user expected.
1078: assert sourceBreakpoints[jbp - 1] < sourceMin : expectedSource;
1079: if (CONSERVATIVE_PIECEWISE) {
1080: canPiecewise = false;
1081: }
1082: }
1083: } else if (j != 0) {
1084: // Found a gap between the last category and the current one. The
1085: // "piecewise" operation may not behave as the user expected for
1086: // sample values falling in this gap.
1087: assert !(expectedSource > sourceMin) : expectedSource;
1088: if (CONSERVATIVE_PIECEWISE) {
1089: canPiecewise = false;
1090: }
1091: }
1092: sourceBreakpoints[jbp] = sourceMin;
1093: sourceBreakpoints[jbp + 1] = sourceMax;
1094: targetBreakpoints[jbp] = targetMin;
1095: targetBreakpoints[jbp + 1] = targetMax;
1096: jbp += 2;
1097: expectedSource = range.getMaximum(false);
1098: expectedTarget = expectedSource * scale
1099: + offset;
1100: }
1101: if (false) {
1102: // HACK: temporarily disabled because 'javac' 1.4.1_02 produces invalid
1103: // bytecode. This bug is fixed in 'java' 1.4.2-beta. Furthermore,
1104: // the "piecewise" operation throws an ArrayIndexOutOfBoundsException
1105: // in JAI code for an unknow reason...
1106: breakpoints[i][0] = sourceBreakpoints = XArray
1107: .resize(sourceBreakpoints, jbp);
1108: breakpoints[i][1] = targetBreakpoints = XArray
1109: .resize(targetBreakpoints, jbp);
1110: assert XArray.isSorted(sourceBreakpoints);
1111: } else {
1112: canPiecewise = false;
1113: }
1114: }
1115: if (canRescale && scales != null) {
1116: operation = "Rescale";
1117: param = param.add(scales).add(offsets);
1118: } else if (canPiecewise && breakpoints != null) {
1119: operation = "Piecewise";
1120: param = param.add(breakpoints);
1121: }
1122: } catch (TransformException exception) {
1123: // At least one category doesn't use a linear relation.
1124: // Ignore the exception and fallback on the next case.
1125: }
1126: /*
1127: * STEP 5 - Transcode the image sample values. The "SampleTranscode" operation is
1128: * registered in the org.geotools.coverage package in the GridSampleDimension
1129: * class.
1130: */
1131: if (operation == null) {
1132: param = param.add(sampleDimensions);
1133: operation = "org.geotools.SampleTranscode";
1134: }
1135: if (LOGGER.isLoggable(AbstractProcessor.OPERATION)) {
1136: // Log a message using the same level than grid coverage processor.
1137: final int index = operation.lastIndexOf('.');
1138: final String shortName = (index >= 0) ? operation
1139: .substring(index + 1) : operation;
1140: final Locale locale = getLocale();
1141: final LogRecord record = Logging
1142: .getResources(locale)
1143: .getLogRecord(
1144: AbstractProcessor.OPERATION,
1145: LoggingKeys.SAMPLE_TRANSCODE_$3,
1146: new Object[] { getName().toString(locale),
1147: new Integer(geo ? 1 : 0), shortName });
1148: record.setSourceClassName(GridCoverage2D.class.getName());
1149: record.setSourceMethodName("geophysics");
1150: LOGGER.log(record);
1151: }
1152: final PlanarImage view = JAI.create(operation, param, hints);
1153: final GridCoverage[] sources = new GridCoverage[] { this };
1154: return new GridCoverage2D(getName(), view, gridGeometry,
1155: targetBands, sources, null);
1156: }
1157:
1158: /**
1159: * Constructs the {@link PlanarImage} from the {@linkplain SerializableRenderedImage}
1160: * after deserialization.
1161: */
1162: private void readObject(final ObjectInputStream in)
1163: throws IOException, ClassNotFoundException {
1164: in.defaultReadObject();
1165: try {
1166: /*
1167: * Set the 'image' field using reflection, because this field is final.
1168: * This is a legal usage for deserialization according Field.set(...)
1169: * documentation in J2SE 1.5.
1170: */
1171: final Field field = GridCoverage2D.class
1172: .getDeclaredField("image");
1173: field.setAccessible(true);
1174: field.set(this , PlanarImage
1175: .wrapRenderedImage(serializedImage));
1176: } catch (NoSuchFieldException cause) {
1177: InvalidClassException e = new InvalidClassException(cause
1178: .getLocalizedMessage());
1179: e.initCause(cause);
1180: throw e;
1181: } catch (IllegalAccessException cause) {
1182: InvalidObjectException e = new InvalidObjectException(cause
1183: .getLocalizedMessage());
1184: e.initCause(cause);
1185: throw e;
1186: }
1187: }
1188:
1189: /**
1190: * Serializes this grid coverage. Before serialization, a {@linkplain SerializableRenderedImage
1191: * serializable rendered image} is created if it was not already done.
1192: */
1193: private void writeObject(final ObjectOutputStream out)
1194: throws IOException {
1195: ensureValid();
1196: if (serializedImage == null) {
1197: RenderedImage source = image;
1198: while (source instanceof RenderedImageAdapter) {
1199: source = ((RenderedImageAdapter) source)
1200: .getWrappedImage();
1201: }
1202: if (source instanceof SerializableRenderedImage) {
1203: serializedImage = (SerializableRenderedImage) source;
1204: } else {
1205: if (tileEncoding == null) {
1206: tileEncoding = "gzip";
1207: }
1208: serializedImage = new SerializableRenderedImage(source,
1209: false, null, tileEncoding, null, null);
1210: final LogRecord record = Logging.format(Level.FINE,
1211: LoggingKeys.CREATED_SERIALIZABLE_IMAGE_$2,
1212: getName(), tileEncoding);
1213: record.setSourceClassName(GridCoverage2D.class
1214: .getName());
1215: record.setSourceMethodName("writeObject");
1216: LOGGER.log(record);
1217: }
1218: }
1219: out.defaultWriteObject();
1220: }
1221:
1222: /**
1223: * Ensures that this coverage has not be {@linkplain #dispose disposed}.
1224: */
1225: private void ensureValid() throws IllegalStateException {
1226: if (disposed) {
1227: // TODO: localize.
1228: throw new IllegalStateException(
1229: "This coverage has been disposed.");
1230: }
1231: }
1232:
1233: /**
1234: * Provides a hint that a coverage will no longer be accessed from a reference in user space.
1235: * This method {@linkplain PlanarImage#dispose disposes} the {@linkplain #image} only if at
1236: * least one of the following conditions is true (otherwise this method do nothing):
1237: * <p>
1238: * <ul>
1239: * <li>{@code force} is {@code true}, <strong>or</strong></li>
1240: * <li>The underlying {@linkplain #image} has no {@linkplain PlanarImage#getSinks sinks}
1241: * other than the views (geophysics, display, <cite>etc.</cite>).</li>
1242: * </ul>
1243: * <p>
1244: * This safety check helps to prevent the disposal of an {@linkplain #image} that still
1245: * used in a JAI operation chain. It doesn't prevent the disposal in every cases however.
1246: * When unsure about whatever a coverage is still in use or not, it is safer to not invoke
1247: * this method and rely on the garbage collector instead.
1248: *
1249: * @see PlanarImage#dispose
1250: *
1251: * @since 2.4
1252: */
1253: //@Override
1254: public synchronized boolean dispose(final boolean force) {
1255: if (disposed) {
1256: // Recursive invocation of this method.
1257: return true;
1258: }
1259: /*
1260: * Checks if this coverage can be disposed. First we get the set of every sinks, which
1261: * may or may not be RenderedImages. Then we remove every sinks that are geophysics or
1262: * other views of this coverage. If there is no remaining sinks, we can process.
1263: */
1264: if (!force) {
1265: Collection/*<?>*/sinks = image.getSinks();
1266: if (sinks != null) {
1267: if (inverse != null) {
1268: sinks = new HashSet(sinks);
1269: sinks.remove(inverse.image);
1270: deepRemove(sinks, inverse.image);
1271: }
1272: if (!sinks.isEmpty()) {
1273: return false;
1274: }
1275: }
1276: }
1277: /*
1278: * No remaining sinks; we can process. First, applies the same procedure on the other
1279: * views (geophysics, display, etc.). If we were able to dispose the views, then we
1280: * can really dispose this coverage.
1281: */
1282: disposed = true; // Must be set before to invoke inverse.dispose().
1283: if (inverse != null && !inverse.dispose(force)) {
1284: disposed = false; // Reset only on success, otherwise the coverage may be invalid.
1285: return false;
1286: }
1287: image.dispose();
1288: return super .dispose(force);
1289: }
1290:
1291: /**
1292: * Removes from the specified collection the specified image and its dependencies.
1293: * This method invokes itself recursively in order to scan down the sources tree.
1294: */
1295: private static void deepRemove(final Collection/*<?>*/sinks,
1296: final RenderedImage image) {
1297: /*
1298: * The following must be unchecked, because PlanarImage.getSources()
1299: * violates RenderedImage.getSources() contract on parameterized type.
1300: */
1301: //@SuppressWarning("unchecked")
1302: final List sources = image.getSources();
1303: if (sources != null) {
1304: for (final Iterator it = sources.iterator(); it.hasNext();) {
1305: final Object dependency = it.next();
1306: sinks.remove(dependency);
1307: if (dependency instanceof RenderedImage) {
1308: deepRemove(sinks, (RenderedImage) dependency);
1309: }
1310: }
1311: }
1312: }
1313: }
|