/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.coverage; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Frame; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.awt.image.renderable.RenderContext; import java.awt.image.renderable.RenderableImage; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.media.jai.ImageFunction; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.PropertySource; import javax.media.jai.PropertySourceImpl; import javax.media.jai.TiledImage; import javax.media.jai.iterator.RectIterFactory; import javax.media.jai.iterator.WritableRectIter; import javax.media.jai.operator.ImageFunctionDescriptor; import org.geotools.geometry.GeneralDirectPosition; import org.geotools.geometry.GeneralEnvelope; import org.geotools.io.LineWriter; import org.geotools.referencing.CRS; import org.geotools.referencing.operation.matrix.GeneralMatrix; import org.geotools.referencing.operation.matrix.XAffineTransform; import org.geotools.resources.Classes; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.image.ImageUtilities; import org.geotools.util.SimpleInternationalString; import org.geotools.util.logging.Logging; import org.opengis.coverage.CannotEvaluateException; import org.opengis.coverage.Coverage; import org.opengis.coverage.PointOutsideCoverageException; import org.opengis.coverage.SampleDimension; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.util.InternationalString; import org.opengis.util.Record; import org.opengis.util.RecordType; /** * Base class of all coverage type. The essential property of coverage is to be able * to generate a value for any point within its domain. How coverage is represented * internally is not a concern. For example consider the following different internal * representations of coverage: * <p> * <ul> * <li>A coverage may be represented by a set of polygons which exhaustively tile a * plane (that is each point on the plane falls in precisely one polygon). The * value returned by the coverage for a point is the value of an attribute of * the polygon that contains the point.</li> * <li>A coverage may be represented by a grid of values. The value returned by the * coverage for a point is that of the grid value whose location is nearest the * point.</li> * <li>Coverage may be represented by a mathematical function. The value returned * by the coverage for a point is just the return value of the function when * supplied the coordinates of the point as arguments.</li> * <li>Coverage may be represented by combination of these. For example, coverage * may be represented by a combination of mathematical functions valid over a * set of polynomials.</li> * </ul> * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public abstract class AbstractCoverage extends PropertySourceImpl implements Coverage { /** * For compatibility during cross-version serialization. */ private static final long serialVersionUID = -2989320942499746295L; /** * The sample dimension to make visible by {@link #getRenderableImage}. */ private static final int VISIBLE_BAND = 0; /** * The coverage name, or {@code null} if none. */ private final InternationalString name; /** * The coordinate reference system, or {@code null} if there is none. */ protected final CoordinateReferenceSystem crs; /** * Constructs a coverage using the specified coordinate reference system. If the coordinate * reference system is {@code null}, then the subclasses must override {@link #getDimension()}. * * @param name * The coverage name, or {@code null} if none. * @param crs * The coordinate reference system. This specifies the CRS used when accessing * a coverage or grid coverage with the {@code evaluate(...)} methods. * @param propertySource * The source for this coverage, or {@code null} if none. Source may be (but is not * limited to) a {@link PlanarImage} or an other {@code AbstractCoverage} object. * @param properties * The set of properties for this coverage, or {@code null} if there is none. * Keys are {@link String} objects ({@link javax.media.jai.util.CaselessStringKey} * are accepted as well), while values may be any {@link Object}. */ protected AbstractCoverage(final CharSequence name, final CoordinateReferenceSystem crs, final PropertySource propertySource, final Map<?,?> properties) { super(properties, propertySource); this.name = SimpleInternationalString.wrap(name); this.crs = crs; } /** * Constructs a new coverage with the same parameters than the specified coverage. * <p> * <strong>Note:</strong> This constructor keeps a strong reference to the source * coverage (through {@link PropertySourceImpl}). * * @param name * The name for this coverage, or {@code null} for the same than {@code coverage}. * @param coverage * The source coverage. */ protected AbstractCoverage(final CharSequence name, final Coverage coverage) { super(null, (coverage instanceof PropertySource) ? (PropertySource) coverage : null); final InternationalString n = SimpleInternationalString.wrap(name); if (coverage instanceof AbstractCoverage) { final AbstractCoverage source = (AbstractCoverage) coverage; this.name = (n != null) ? n : source.name; this.crs = source.crs; } else { this.name = (n != null) ? n : new SimpleInternationalString(coverage.toString()); this.crs = coverage.getCoordinateReferenceSystem(); } } /** * Returns the coverage name, or {@code null} if none. The default * implementation returns the name specified at construction time. * * @return The coverage name, or {@code null}. */ public InternationalString getName() { return name; } /** * Returns the dimension of this coverage. This is a shortcut for * <code>{@linkplain #crs}.getCoordinateSystem().getDimension()</code>. * * @return The dimension of this coverage. */ public final int getDimension() { return crs.getCoordinateSystem().getDimension(); } /** * Returns the coordinate reference system to which the objects in its domain are * referenced. This is the CRS used when accessing a coverage or grid coverage with * the {@code evaluate(...)} methods. This coordinate reference system is usually * different than coordinate system of the grid. It is the target coordinate reference * system of the {@link org.opengis.coverage.grid.GridGeometry#getGridToCRS gridToCRS} * math transform. * <p> * Grid coverage can be accessed (re-projected) with new coordinate reference system * with the {@link org.opengis.coverage.processing.GridCoverageProcessor} component. * In this case, a new instance of a grid coverage is created. * * @return The coordinate reference system used when accessing a coverage or * grid coverage with the {@code evaluate(...)} methods. * * @see org.geotools.coverage.grid.GeneralGridGeometry#getGridToCRS */ public CoordinateReferenceSystem getCoordinateReferenceSystem() { return crs; } /** * Returns the bounding box for the coverage domain in * {@linkplain #getCoordinateReferenceSystem coordinate reference system} coordinates. May * be {@code null} if this coverage has no associated coordinate reference system. For grid * coverages, the grid cells are centered on each grid coordinate. The envelope for a 2-D * grid coverage includes the following corner positions. * * <blockquote><pre> * (Minimum row - 0.5, Minimum column - 0.5) for the minimum coordinates * (Maximum row - 0.5, Maximum column - 0.5) for the maximum coordinates * </pre></blockquote> * * The default implementation returns the domain of validity of the CRS, if there is one. * * @return The bounding box for the coverage domain in coordinate system coordinates. */ public Envelope getEnvelope() { return CRS.getEnvelope(crs); } /** * Describes the range of the coverage. It consists of a list of attribute name/data type pairs. * A simple list is the most common form of range type, but {@code RecordType} can be used * recursively to describe more complex structures. The range type for a specific coverage * shall be specified in an application schema. * <p> * <strong>This method is not yet implemented.</strong> * * @since 2.3 */ public RecordType getRangeType() { throw unsupported(); } /** * Invoked when an unsupported operation is invoked. */ private static final UnsupportedOperationException unsupported() { throw new UnsupportedOperationException( "This method is currently not implemented. " + "It may be implemented by next version of coverage module."); } /** * Returns a localized error message for the specified array. */ private static String formatErrorMessage(final Object array) { Class<?> type = null; if (array != null) { type = array.getClass(); if (type.isArray()) { type = type.getComponentType(); } } return Errors.format(ErrorKeys.CANT_CONVERT_FROM_TYPE_$1, type); } /** * Returns a set of records of feature attribute values for the specified direct position. The * parameter {@code list} is a sequence of feature attribute names each of which identifies a * field of the range type. If {@code list} is null, the operation shall return a value for * every field of the range type. Otherwise, it shall return a value for each field included in * {@code list}. If the direct position passed is not in the domain of the coverage, then an * exception is thrown. If the input direct position falls within two or more geometric objects * within the domain, the operation shall return records of feature attribute values computed * according to the {@linkplain #getCommonPointRule common point rule}. * <P> * <B>NOTE:</B> Normally, the operation will return a single record of feature attribute values. * <p> * <strong>This method is not yet implemented.</strong> * * @since 2.3 */ public Set<Record> evaluate(final DirectPosition p, final Collection<String> list) { throw unsupported(); } /** * Returns a sequence of boolean values for a given point in the coverage. A value for each * {@linkplain SampleDimension sample dimension} is included in the sequence. The default * interpolation type used when accessing grid values for points which fall between grid cells * is {@linkplain javax.media.jai.InterpolationNearest nearest neighbor}, but it can be changed * by some {@linkplain org.geotools.coverage.grid.Interpolator2D subclasses}. The CRS of the * point is the same as the grid coverage {@linkplain #getCoordinateReferenceSystem coordinate * reference system}. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or {@code null} to create a new array. * @return The {@code dest} array, or a newly created array if {@code dest} was null. * @throws PointOutsideCoverageException if the evaluation failed because the input point * has invalid coordinates. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate * for an other reason. It may be thrown if the coverage data type can't be converted * to {@code boolean} by an identity or widening conversion. Subclasses may relax this * constraint if appropriate. */ public boolean[] evaluate(final DirectPosition coord, boolean[] dest) throws PointOutsideCoverageException, CannotEvaluateException { final Object array = evaluate(coord); try { final int length = Array.getLength(array); if (dest == null) { dest = new boolean[length]; } for (int i=0; i<length; i++) { dest[i] = Array.getBoolean(array, i); } } catch (IllegalArgumentException exception) { throw new CannotEvaluateException(formatErrorMessage(array), exception); } return dest; } /** * Returns a sequence of byte values for a given point in the coverage. A value for each * {@linkplain SampleDimension sample dimension} is included in the sequence. The default * interpolation type used when accessing grid values for points which fall between grid cells * is {@linkplain javax.media.jai.InterpolationNearest nearest neighbor}, but it can be changed * by some {@linkplain org.geotools.coverage.grid.Interpolator2D subclasses}. The CRS of the * point is the same as the grid coverage {@linkplain #getCoordinateReferenceSystem coordinate * reference system}. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or {@code null} to create a new array. * @return The {@code dest} array, or a newly created array if {@code dest} was null. * @throws PointOutsideCoverageException if the evaluation failed because the input point * has invalid coordinates. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate * for an other reason. It may be thrown if the coverage data type can't be converted * to {@code byte} by an identity or widening conversion. Subclasses may relax this * constraint if appropriate. */ public byte[] evaluate(final DirectPosition coord, byte[] dest) throws PointOutsideCoverageException, CannotEvaluateException { final Object array = evaluate(coord); try { final int length = Array.getLength(array); if (dest == null) { dest = new byte[length]; } for (int i=0; i<length; i++) { dest[i] = Array.getByte(array, i); } } catch (IllegalArgumentException exception) { throw new CannotEvaluateException(formatErrorMessage(array), exception); } return dest; } /** * Returns a sequence of integer values for a given point in the coverage. A value for each * {@linkplain SampleDimension sample dimension} is included in the sequence. The default * interpolation type used when accessing grid values for points which fall between grid cells * is {@linkplain javax.media.jai.InterpolationNearest nearest neighbor}, but it can be changed * by some {@linkplain org.geotools.coverage.grid.Interpolator2D subclasses}. The CRS of the * point is the same as the grid coverage {@linkplain #getCoordinateReferenceSystem coordinate * reference system}. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or {@code null} to create a new array. * @return The {@code dest} array, or a newly created array if {@code dest} was null. * @throws PointOutsideCoverageException if the evaluation failed because the input point * has invalid coordinates. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate * for an other reason. It may be thrown if the coverage data type can't be converted * to {@code int} by an identity or widening conversion. Subclasses may relax this * constraint if appropriate. */ public int[] evaluate(final DirectPosition coord, int[] dest) throws PointOutsideCoverageException, CannotEvaluateException { final Object array = evaluate(coord); try { final int length = Array.getLength(array); if (dest == null) { dest = new int[length]; } for (int i=0; i<length; i++) { dest[i] = Array.getInt(array, i); } } catch (IllegalArgumentException exception) { throw new CannotEvaluateException(formatErrorMessage(array), exception); } return dest; } /** * Returns a sequence of float values for a given point in the coverage. A value for each * {@linkplain SampleDimension sample dimension} is included in the sequence. The default * interpolation type used when accessing grid values for points which fall between grid cells * is {@linkplain javax.media.jai.InterpolationNearest nearest neighbor}, but it can be changed * by some {@linkplain org.geotools.coverage.grid.Interpolator2D subclasses}. The CRS of the * point is the same as the grid coverage {@linkplain #getCoordinateReferenceSystem coordinate * reference system}. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or {@code null} to create a new array. * @return The {@code dest} array, or a newly created array if {@code dest} was null. * @throws PointOutsideCoverageException if the evaluation failed because the input point * has invalid coordinates. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate * for an other reason. It may be thrown if the coverage data type can't be converted * to {@code float} by an identity or widening conversion. Subclasses may relax this * constraint if appropriate. */ public float[] evaluate(final DirectPosition coord, float[] dest) throws PointOutsideCoverageException, CannotEvaluateException { final Object array = evaluate(coord); try { final int length = Array.getLength(array); if (dest == null) { dest = new float[length]; } for (int i=0; i<length; i++) { dest[i] = Array.getFloat(array, i); } } catch (IllegalArgumentException exception) { throw new CannotEvaluateException(formatErrorMessage(array), exception); } return dest; } /** * Returns a sequence of double values for a given point in the coverage. A value for each * {@linkplain SampleDimension sample dimension} is included in the sequence. The default * interpolation type used when accessing grid values for points which fall between grid cells * is {@linkplain javax.media.jai.InterpolationNearest nearest neighbor}, but it can be changed * by some {@linkplain org.geotools.coverage.grid.Interpolator2D subclasses}. The CRS of the * point is the same as the grid coverage {@linkplain #getCoordinateReferenceSystem coordinate * reference system}. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or {@code null} to create a new array. * @return The {@code dest} array, or a newly created array if {@code dest} was null. * @throws PointOutsideCoverageException if the evaluation failed because the input point * has invalid coordinates. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate * for an other reason. It may be thrown if the coverage data type can't be converted * to {@code double} by an identity or widening conversion. Subclasses may relax this * constraint if appropriate. */ public double[] evaluate(final DirectPosition coord, double[] dest) throws PointOutsideCoverageException, CannotEvaluateException { final Object array = evaluate(coord); try { final int length = Array.getLength(array); if (dest == null) { dest = new double[length]; } for (int i=0; i<length; i++) { dest[i] = Array.getDouble(array, i); } } catch (IllegalArgumentException exception) { throw new CannotEvaluateException(formatErrorMessage(array), exception); } return dest; } /** * Returns 2D view of this grid coverage as a renderable image. This method * allows interoperability with Java2D. * * @param xAxis Dimension to use for the <var>x</var> display axis. * @param yAxis Dimension to use for the <var>y</var> display axis. * @return A 2D view of this grid coverage as a renderable image. */ public RenderableImage getRenderableImage(final int xAxis, final int yAxis) { return new Renderable(xAxis, yAxis); } ///////////////////////////////////////////////////////////////////////// //////////////// //////////////// //////////////// RenderableImage / ImageFunction //////////////// //////////////// //////////////// ///////////////////////////////////////////////////////////////////////// /** * A view of a {@linkplain AbstractCoverage coverage} as a renderable image. Renderable images * allow interoperability with <A HREF="http://java.sun.com/products/java-media/2D/">Java2D</A> * for a two-dimensional slice of a coverage (which may or may not be a * {@linkplain org.geotools.coverage.grid.GridCoverage2D grid coverage}). * * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see AbstractCoverage#getRenderableImage */ protected class Renderable extends PropertySourceImpl implements RenderableImage, ImageFunction { /** * For compatibility during cross-version serialization. */ private static final long serialVersionUID = -6661389795161502552L; /** * The two dimensional view of the coverage's envelope. */ private final Rectangle2D bounds; /** * Dimension to use for <var>x</var> axis. */ protected final int xAxis; /** * Dimension to use for <var>y</var> axis. */ protected final int yAxis; /** * A coordinate point where to evaluate the function. The point dimension is equals to the * {@linkplain AbstractCoverage#getDimension coverage's dimension}. The {@linkplain #xAxis * x} and {@link #yAxis y} ordinates will be ignored, since they will vary for each pixel * to be evaluated. Other ordinates, if any, should be set to a fixed value. For example a * coverage may be three-dimensional, where the third dimension is the time axis. In such * case, {@code coordinate.ord[2]} should be set to the point in time where to evaluate the * coverage. By default, all ordinates are initialized to 0. Subclasses should set the * desired values in their constructor if needed. */ protected final GeneralDirectPosition coordinate = new GeneralDirectPosition(getDimension()); /** * Constructs a renderable image. * * @param xAxis Dimension to use for <var>x</var> axis. * @param yAxis Dimension to use for <var>y</var> axis. */ public Renderable(final int xAxis, final int yAxis) { super(null, AbstractCoverage.this); this.xAxis = xAxis; this.yAxis = yAxis; final Envelope envelope = getEnvelope(); bounds = new Rectangle2D.Double(envelope.getMinimum(xAxis), envelope.getMinimum(yAxis), envelope.getSpan (xAxis), envelope.getSpan (yAxis)); } /** * Returns {@code null} to indicate that no source information is available. */ public Vector<RenderableImage> getSources() { return null; } /** * Returns {@code true} if successive renderings with the same arguments * may produce different results. The default implementation returns {@code false}. * * @see org.geotools.coverage.grid.GridCoverage2D#isDataEditable */ public boolean isDynamic() { return false; } /** * Returns {@code false} since values are not complex. * * @return Always {@code false} in default implementation. */ public boolean isComplex() { return false; } /** * Gets the width in coverage coordinate space. * * @see AbstractCoverage#getEnvelope * @see AbstractCoverage#getCoordinateReferenceSystem */ public float getWidth() { return (float) bounds.getWidth(); } /** * Gets the height in coverage coordinate space. * * @see AbstractCoverage#getEnvelope * @see AbstractCoverage#getCoordinateReferenceSystem */ public float getHeight() { return (float) bounds.getHeight(); } /** * Gets the minimum <var>X</var> coordinate of the rendering-independent image * data. This is the {@linkplain AbstractCoverage#getEnvelope coverage's envelope} * minimal value for the {@linkplain #xAxis x axis}. * * @see AbstractCoverage#getEnvelope * @see AbstractCoverage#getCoordinateReferenceSystem */ public float getMinX() { return (float) bounds.getX(); } /** * Gets the minimum <var>Y</var> coordinate of the rendering-independent image * data. This is the {@linkplain AbstractCoverage#getEnvelope coverage's envelope} * minimal value for the {@linkplain #yAxis y axis}. * * @see AbstractCoverage#getEnvelope * @see AbstractCoverage#getCoordinateReferenceSystem */ public float getMinY() { return (float) bounds.getY(); } /** * Returns a rendered image with a default width and height in pixels. * * @return A rendered image containing the rendered data */ public RenderedImage createDefaultRendering() { return createScaledRendering(512, 0, null); } /** * Creates a rendered image with width {@code width} and height {@code height} in pixels. * If {@code width} is 0, it will be computed automatically from {@code height}. Conversely, * if {@code height} is 0, il will be computed automatically from {@code width}. * <p> * The default implementation creates a render context with {@link #createRenderContext} * and invokes {@link #createRendering(RenderContext)}. * * @param width The width of rendered image in pixels, or 0. * @param height The height of rendered image in pixels, or 0. * @param hints Rendering hints, or {@code null}. * @return A rendered image containing the rendered data */ public RenderedImage createScaledRendering(int width, int height, final RenderingHints hints) { final double boundsWidth = bounds.getWidth(); final double boundsHeight = bounds.getHeight(); if (!(width > 0)) { // Use '!' in order to catch NaN if (!(height > 0)) { throw new IllegalArgumentException(Errors.format( ErrorKeys.UNSPECIFIED_IMAGE_SIZE)); } width = (int) Math.round(height * (boundsWidth / boundsHeight)); } else if (!(height > 0)) { height = (int) Math.round(width * (boundsHeight / boundsWidth)); } return createRendering(createRenderContext(new Rectangle(0, 0, width, height), hints)); } /** * Creates a rendered image using a given render context. This method will uses an * "{@link ImageFunctionDescriptor ImageFunction}" operation if possible (i.e. if * the area of interect is rectangular and the affine transform contains only * translation and scale coefficients). * * @param context The render context to use to produce the rendering. * @return A rendered image containing the rendered data */ public RenderedImage createRendering(final RenderContext context) { final AffineTransform crsToGrid = context.getTransform(); final Shape area = context.getAreaOfInterest(); final Rectangle gridBounds; if (true) { /* * Computes the grid bounds for the coverage bounds (or the area of interest). * The default implementation of Rectangle uses Math.floor and Math.ceil for * computing a box which contains fully the Rectangle2D. But in our particular * case, we really want to round toward the nearest integer. */ final Rectangle2D bounds = XAffineTransform.transform(crsToGrid, (area != null) ? area.getBounds2D() : this.bounds, null); final int xmin = (int) Math.round(bounds.getMinX()); final int ymin = (int) Math.round(bounds.getMinY()); final int xmax = (int) Math.round(bounds.getMaxX()); final int ymax = (int) Math.round(bounds.getMaxY()); gridBounds = new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin); } /* * Computes some properties of the image to be created. */ final Dimension tileSize = ImageUtilities.toTileSize(gridBounds.getSize()); final GridSampleDimension band = GridSampleDimension.wrap(getSampleDimension(VISIBLE_BAND)); final ColorModel colorModel = band.getColorModel(VISIBLE_BAND, getNumSampleDimensions()); final SampleModel sampleModel = colorModel.createCompatibleSampleModel(tileSize.width, tileSize.height); /* * If the image can be created using the ImageFunction operation, do it. * It allow JAI to defer the computation until a tile is really requested. */ final PlanarImage image; if ((area == null || area instanceof Rectangle2D) && crsToGrid.getShearX() == 0 && crsToGrid.getShearY() == 0) { image = ImageFunctionDescriptor.create(this, // The functional description gridBounds.width, // The image width gridBounds.height, // The image height (float) (1/crsToGrid.getScaleX()), // The X scale factor (float) (1/crsToGrid.getScaleY()), // The Y scale factor (float) crsToGrid.getTranslateX(), // The X translation (float) crsToGrid.getTranslateY(), // The Y translation new RenderingHints(JAI.KEY_IMAGE_LAYOUT, new ImageLayout() .setMinX (gridBounds.x) .setMinY (gridBounds.y) .setTileWidth (tileSize.width) .setTileHeight (tileSize.height) .setSampleModel(sampleModel) .setColorModel (colorModel))); } else { /* * Creates immediately a rendered image using a given render context. This block * is run when the image can't be created with JAI's ImageFunction operator, for * example because the affine transform swap axis or because there is an area of * interest. */ // Clones the coordinate point in order to allow multi-thread // invocation. final GeneralDirectPosition coordinate = new GeneralDirectPosition(this.coordinate); final TiledImage tiled = new TiledImage(gridBounds.x, gridBounds.y, gridBounds.width, gridBounds.height, 0, 0, sampleModel, colorModel); final Point2D.Double point2D = new Point2D.Double(); final int numBands = tiled.getNumBands(); final double[] samples = new double[numBands]; final double[] padNaNs = new double[numBands]; Arrays.fill(padNaNs, Double.NaN); final WritableRectIter iterator = RectIterFactory.createWritable(tiled, gridBounds); if (!iterator.finishedLines()) try { int y = gridBounds.y; do { iterator.startPixels(); if (!iterator.finishedPixels()) { int x = gridBounds.x; do { point2D.x = x; point2D.y = y; crsToGrid.inverseTransform(point2D, point2D); if (area == null || area.contains(point2D)) { coordinate.ordinates[xAxis] = point2D.x; coordinate.ordinates[yAxis] = point2D.y; iterator.setPixel(evaluate(coordinate, samples)); } else { iterator.setPixel(padNaNs); } x++; } while (!iterator.nextPixelDone()); assert (x == gridBounds.x + gridBounds.width); y++; } } while (!iterator.nextLineDone()); assert (y == gridBounds.y + gridBounds.height); } catch (NoninvertibleTransformException exception) { throw new IllegalArgumentException(Errors.format( ErrorKeys.ILLEGAL_ARGUMENT_$1, "context"), exception); } image = tiled; } /* * Adds a 'gridToCRS' property to the image. This is an important * information for constructing a GridCoverage from this image later. */ try { image.setProperty("gridToCRS", crsToGrid.createInverse()); } catch (NoninvertibleTransformException exception) { // Can't add the property. Too bad, the image has been created // anyway. Maybe the user know what he is doing... Logging.unexpectedException(Renderable.class, "createRendering", exception); } return image; } /** * Initializes a render context with an affine transform that maps the coverage envelope * to the specified destination rectangle. The affine transform mays swap axis in order to * normalize their order (i.e. make them appear in the (<var>x</var>,<var>y</var>) order), * so that the image appears properly oriented when rendered. * * @param gridBounds The two-dimensional destination rectangle. * @param hints The rendering hints, or {@code null} if none. * @return A render context initialized with an affine transform from the coverage * to the grid coordinate system. This transform is the inverse of * {@link org.geotools.coverage.grid.GridGeometry2D#getGridToCRS2D}. * * @see org.geotools.coverage.grid.GridGeometry2D#getGridToCRS2D */ protected RenderContext createRenderContext(final Rectangle2D gridBounds, final RenderingHints hints) { final GeneralMatrix matrix; final GeneralEnvelope srcEnvelope = new GeneralEnvelope(bounds); final GeneralEnvelope dstEnvelope = new GeneralEnvelope(gridBounds); if (crs != null) { final CoordinateSystem cs = crs.getCoordinateSystem(); final AxisDirection[] axis = new AxisDirection[] { cs.getAxis(xAxis).getDirection(), cs.getAxis(yAxis).getDirection() }; final AxisDirection[] normalized = axis.clone(); if (false) { // Normalize axis: Is it really a good idea? // We should provide a rendering hint for configuring that. Arrays.sort(normalized); for (int i = normalized.length; --i >= 0;) { normalized[i] = normalized[i].absolute(); } } normalized[1] = normalized[1].opposite(); // Image's Y axis is downward. matrix = new GeneralMatrix(srcEnvelope, axis, dstEnvelope, normalized); } else { matrix = new GeneralMatrix(srcEnvelope, dstEnvelope); } return new RenderContext(matrix.toAffineTransform2D(), hints); } /** * Returns the number of elements per value at each position. This is * the maximum value plus 1 allowed in {@code getElements(...)} methods * invocation. The default implementation returns the number of sample * dimensions in the coverage. * * @return The number of sample dimensions. */ public int getNumElements() { return getNumSampleDimensions(); } /** * Returns all values of a given element for a specified set of coordinates. * This method is automatically invoked at rendering time for populating an * image tile, providing that the rendered image is created using the * "{@link ImageFunctionDescriptor ImageFunction}" operator and the image * type is not {@code double}. The default implementation invokes * {@link AbstractCoverage#evaluate(DirectPosition,float[])} recursively. */ public void getElements(final float startX, final float startY, final float deltaX, final float deltaY, final int countX, final int countY, final int element, final float[] real, final float[] imag) { int index = 0; float[] buffer = null; // Clones the coordinate point in order to allow multi-thread invocation. final GeneralDirectPosition coordinate = new GeneralDirectPosition(this.coordinate); coordinate.ordinates[1] = startY; for (int j=0; j<countY; j++) { coordinate.ordinates[0] = startX; for (int i=0; i<countX; i++) { buffer = evaluate(coordinate, buffer); real[index++] = buffer[element]; coordinate.ordinates[0] += deltaX; } coordinate.ordinates[1] += deltaY; } } /** * Returns all values of a given element for a specified set of coordinates. * This method is automatically invoked at rendering time for populating an * image tile, providing that the rendered image is created using the * "{@link ImageFunctionDescriptor ImageFunction}" operator and the image * type is {@code double}. The default implementation invokes * {@link AbstractCoverage#evaluate(DirectPosition,double[])} recursively. */ public void getElements(final double startX, final double startY, final double deltaX, final double deltaY, final int countX, final int countY, final int element, final double[] real, final double[] imag) { int index = 0; double[] buffer = null; // Clones the coordinate point in order to allow multi-thread invocation. final GeneralDirectPosition coordinate = new GeneralDirectPosition(this.coordinate); coordinate.ordinates[1] = startY; for (int j=0; j<countY; j++) { coordinate.ordinates[0] = startX; for (int i=0; i<countX; i++) { buffer = evaluate(coordinate, buffer); real[index++] = buffer[element]; coordinate.ordinates[0] += deltaX; } coordinate.ordinates[1] += deltaY; } } } /** * Display this coverage in a windows. This convenience method is used for debugging purpose. * The exact appareance of the windows and the tools provided may changes in future versions. * * @param title The window title, or {@code null} for default value. * @param xAxis Dimension to use for the <var>x</var> display axis. * @param yAxis Dimension to use for the <var>y</var> display axis. * * @since 2.3 */ public void show(String title, final int xAxis, final int yAxis) { if (title == null || (title = title.trim()).length() == 0) { title = String.valueOf(getName()); } // In the following line, the constructor display immediately the viewer. new Viewer(title, getRenderableImage(xAxis, yAxis).createDefaultRendering()); } /** * A trivial viewer implementation to be used by {@link AbstractCoverage#show(String,int,int)} * method. * <p> * <strong>Implementation note:</strong> * We use AWT Frame, not Swing JFrame, because {@link ScrollingImagePane} is an AWT * component. Swing is an overhead in this context without clear benefict. Note also * that {@code ScrollingImagePanel} includes the scroll bar, so there is no need to * put this component in an other {@code JScrollPane}. */ private static final class Viewer extends WindowAdapter implements Runnable { /** * The frame to dispose once closed. */ private final Frame frame; /** * Displays the specified image in a window with the specified title. */ @SuppressWarnings("deprecation") public Viewer(final String title, final RenderedImage image) { final int width = Math.max(Math.min(image.getWidth(), 800), 24); final int height = Math.max(Math.min(image.getHeight(), 600), 24); frame = new Frame(title); frame.add(new javax.media.jai.widget.ScrollingImagePanel(image, width, height)); frame.addWindowListener(this); EventQueue.invokeLater(this); } /** * Display the window in the event queue. * Required because 'pack()' is invoked before 'setVisible(true)'. */ public void run() { frame.pack(); frame.setVisible(true); } /** * Invoked when the user dispose the window. */ @Override public void windowClosing(WindowEvent e) { frame.dispose(); } } /** * Display this coverage in a windows. This convenience method is used for * debugging purpose. The exact appareance of the windows and the tools * provided may changes in future versions. * * @param title The window title, or {@code null} for default value. * * @since 2.3 */ public void show(final String title) { show(title, 0, 1); } /** * Displays this coverage in a windows. This convenience method is used for debugging purpose. * The exact appareance of the windows and the tools provided may changes in future versions. */ public void show() { show(null); } /** * Returns the source data for a coverage. The default implementation returns an empty list. */ public List<? extends Coverage> getSources() { return Collections.emptyList(); } /** * Returns the default locale for logging, error messages, <cite>etc.</cite>. * * @return The default locale for logging and error message. */ public Locale getLocale() { return Locale.getDefault(); } /** * Returns a string representation of this coverage. This string is for * debugging purpose only and may change in future version. */ @Override public String toString() { final StringWriter out = new StringWriter(); out.write(Classes.getShortClassName(this)); out.write("[\""); out.write(String.valueOf(getName())); out.write('"'); final Envelope envelope = getEnvelope(); if (envelope != null) { out.write(", "); out.write(envelope.toString()); } if (crs != null) { out.write(", "); out.write(Classes.getShortClassName(crs)); out.write("[\""); out.write(crs.getName().getCode()); out.write("\"]"); } out.write(']'); final String lineSeparator = System.getProperty("line.separator", "\n"); final LineWriter filter = new LineWriter(out, lineSeparator + "\u2502 "); final int n = getNumSampleDimensions(); try { filter.write(lineSeparator); for (int i=0; i<n; i++) { filter.write(getSampleDimension(i).toString()); } filter.flush(); } catch (IOException exception) { // Should not happen throw new AssertionError(exception); } final StringBuffer buffer = out.getBuffer(); buffer.setLength(buffer.lastIndexOf(lineSeparator) + lineSeparator.length()); return buffer.toString(); } /** * Provides a hint that a coverage will no longer be accessed from a reference in user space. * This can be used as a hint in situations where waiting for garbage collection would be * overly conservative. The results of referencing a coverage after a call to {@code dispose} * are undefined, except if this method returned {@code false}. * <p> * This method can work in a <cite>conservative</cite> mode or a <cite>forced</cite> mode, * determined by the {@code force} argument: * * <ul> * <li><p>If {@code force} is {@code false} (the recommanded value), this method may process * only under some conditions. For example a grid coverage may dispose its planar image only * if it has no {@linkplain PlanarImage#getSinks sinks}. This method returns {@code true} if * it disposed all resources, or {@code false} if this method vetoed against the disposal. * In the later case, this coverage can still be used.</p></li> * * <li><p>If {@code force} is {@code true}, then this method processes inconditionnally and * returns always {@code true}. This is a more risky behavior.</p></li> * </ul> * <p> * The conservative mode ({@code force = false}) performs its safety checks on a * <cite>best-effort</cite> basis, with no guarantees. Therefore, it would be wrong to write * a program that depended on the safety checks for its correctness. In case of doubt about * whatever this coverage still in use or not, it is safer to rely on the garbage collector. * * @param force {@code true} for forcing an inconditionnal disposal, or {@code false} for * performing a conservative disposal. The recommanded value is {@code false}. * @return {@code true} if this method disposed at least some resources, or {@code false} * if this method vetoed against the disposal. * * @see PlanarImage#dispose * * @since 2.4 */ public boolean dispose(boolean force) { return true; } }