/* * Geotools 2 - OpenSource mapping toolkit * (C) 2003, Geotools Project Management Committee (PMC) * (C) 2001, Institut de Recherche pour le Développement * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.gp; // J2SE dependencies import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationNearest; import javax.media.jai.ParameterList; import javax.media.jai.ParameterListDescriptor; import javax.media.jai.ParameterListDescriptorImpl; import javax.media.jai.iterator.RectIter; import javax.media.jai.iterator.RectIterFactory; import org.geotools.ct.MathTransform2D; import org.geotools.cv.PointOutsideCoverageException; import org.geotools.gc.GridCoverage; import org.opengis.coverage.CannotEvaluateException; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.operation.TransformException; /** * A grid coverage using an {@link Interpolation} for evaluating points. * This interpolator do not work for nearest-neighbor * interpolation (use the standard {@link GridCoverage} class for that). * It should work for other kinds of interpolation however. * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux * * @deprecated Replaced by {@link org.geotools.coverage.operation.Interpolator2D}. */ final class Interpolator extends GridCoverage { /** * The greatest value smaller than 1 representable as a float number. * This value can be obtained with org.geotools.resources.XMath.previous(1f). */ private static final float ONE_EPSILON = 0.99999994f; /** * Transform from "real world" coordinates to grid coordinates. * This transform maps coordinates to pixel centers. */ private final MathTransform2D toGrid; /** * The interpolation method. */ private final Interpolation interpolation; /** * Second interpolation method to use if this one failed. * May be null if there is no fallback. By * convention, this means that interpolation * should fallback on super.evaluate(...) * (i.e. nearest neighbor). */ private final Interpolator fallback; /** * Image bounds. Bounds have been reduced * by {@link Interpolation}'s padding. */ private final int xmin, ymin, xmax, ymax; /** * Interpolation padding. */ private final int top, left; /** * The interpolation bounds. Interpolation will use pixel inside * this rectangle. This rectangle is passed as an argument to * {@link RectIterFactory}. */ private final Rectangle bounds; /** * Arrays to use for passing arguments to interpolation. * This array will be constructed only when first needed. */ private transient double[][] doubles; /** * Arrays to use for passing arguments to interpolation. * This array will be constructed only when first needed. */ private transient float[][] floats; /** * Arrays to use for passing arguments to interpolation. * This array will be constructed only when first needed. */ private transient int[][] ints; /** * Construct a new interpolator. * * @param coverage The coverage to interpolate. * @param interpolations The interpolations to use and its fallback (if any). */ public static GridCoverage create(GridCoverage coverage, final Interpolation[] interpolations) { if (coverage instanceof Interpolator) { coverage = ((Interpolator)coverage).getSource(); } if (interpolations.length==0 || (interpolations[0] instanceof InterpolationNearest)) { return coverage; } return new Interpolator(coverage, interpolations, 0); } /** * Construct a new interpolator for the specified interpolation. * * @param coverage The coverage to interpolate. * @param interpolations The interpolations to use and its fallback * (if any). This array must have at least 1 element. * @param index The index of interpolation to use in the interpolations array. */ private Interpolator(final GridCoverage coverage, final Interpolation[] interpolations, final int index) { super(coverage); this.interpolation = interpolations[index]; if (index+1 < interpolations.length) { if (interpolations[index+1] instanceof InterpolationNearest) { // By convention, 'fallback==this' is for 'super.evaluate(...)' // (i.e. "NearestNeighbor"). this.fallback = this; } else { this.fallback = new Interpolator(coverage, interpolations, index+1); } } else { this.fallback = null; } /* * Compute the affine transform from "real world" coordinates to grid coordinates. * This transform maps coordinates to pixel centers. If this transform has * already be created during fallback construction, reuse the fallback's instance * instead of creating a new identical one. */ if (fallback!=null && fallback!=this) { this.toGrid = fallback.toGrid; } else try { final MathTransform2D transform = gridGeometry.getGridToCoordinateSystem2D(); // Note: If we want nearest-neighbor interpolation, we need to add the // following line (assuming the transform is an 'AffineTransform'): // // transform.translate(-0.5, -0.5); // // This is because we need to cancel the last 'translate(0.5, 0.5)' that appears // in GridGeometry's constructor (we must remember that OpenGIS's transform maps // pixel CENTER, while JAI transforms maps pixel UPPER LEFT corner). For exemple // the (12.4, 18.9) coordinates still lies on the [12,9] pixel. Since the JAI's // nearest-neighbor interpolation use 'Math.floor' operation instead of // 'Math.round', we must follow this convention. // // For other kinds of interpolation, we want to maps pixel values to pixel center. // For example, coordinate (12.5, 18.5) (in floating-point coordinates) lies at // the center of pixel [12,18] (in integer coordinates); the evaluated value // should be the exact pixel's value. On the other hand, coordinate (12.5, 19) // (in floating-point coordinates) lies exactly at the edge between pixels // [12,19] and [12,20]; the evaluated value should be a mid-value between those // two pixels. If we want center of mass located at pixel centers, we must keep // the (0.5, 0.5) translation provided by 'GridGeometry' for interpolation other // than nearest-neighbor. toGrid = (MathTransform2D) transform.inverse(); } catch (NoninvertibleTransformException exception) { final IllegalArgumentException e = new IllegalArgumentException(); e.initCause(exception); throw e; } final int left = interpolation.getLeftPadding(); final int right = interpolation.getRightPadding(); final int top = interpolation.getTopPadding(); final int bottom = interpolation.getBottomPadding(); this.top = top; this.left = left; final int x = image.getMinX(); final int y = image.getMinY(); this.xmin = x + left; this.ymin = y + top; this.xmax = x + image.getWidth() - right; this.ymax = y + image.getHeight() - bottom; bounds = new Rectangle(0, 0, interpolation.getWidth(), interpolation.getHeight()); } /** * Returns the source grid coverage. */ private GridCoverage getSource() { final GridCoverage[] sources = getSources(); assert sources.length == 1 : sources.length; return sources[0]; } /** * Invoked by {@link #geophysics(boolean)} when the packed or geophysics companion of this * grid coverage need to be created. This method apply to the new grid coverage the same * interpolation than this grid coverage. * * @param geo true to get a grid coverage with sample values equals to * geophysics values, or false to get the packed version. * @return The newly created grid coverage. */ protected GridCoverage createGeophysics(final boolean geo) { return create(getSource().geophysics(geo), getInterpolations()); } /** * Returns interpolations. The first array's element is the * interpolation for this grid coverage. Other elements (if * any) are fallbacks. */ public Interpolation[] getInterpolations() { final List interp = new ArrayList(); Interpolator scan = this; do { interp.add(interpolation); if (scan.fallback == scan) { interp.add(Interpolation.getInstance(Interpolation.INTERP_NEAREST)); break; } scan = scan.fallback; } while (scan != null); return (Interpolation[]) interp.toArray(new Interpolation[interp.size()]); } /** * Returns the name of the interpolation used by this {@link Interpolator}. */ public String getInterpolationName() { return Operation.getInterpolationName(interpolation); } /** * Return an sequence of integer values for a given two-dimensional point in the coverage. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or null. * @return An array containing values. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate. * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation * failed because the input point has invalid coordinates. */ public int[] evaluate(final Point2D coord, int[] dest) throws CannotEvaluateException { if (fallback != null) { dest = super.evaluate(coord, dest); } try { final Point2D pixel = toGrid.transform(coord, null); final double x = pixel.getX(); final double y = pixel.getY(); if (!Double.isNaN(x) && !Double.isNaN(y)) { dest = interpolate(x, y, dest, 0, image.getNumBands()); if (dest != null) { return dest; } } } catch (TransformException exception) { throw new CannotEvaluateException(/*coord*/ null, exception); // TODO } throw new PointOutsideCoverageException(coord); } /** * Return an sequence of float values for a given two-dimensional point in the coverage. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or null. * @return An array containing values. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate. * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation * failed because the input point has invalid coordinates. */ public float[] evaluate(final Point2D coord, float[] dest) throws CannotEvaluateException { if (fallback!=null) { dest = super.evaluate(coord, dest); } try { final Point2D pixel = toGrid.transform(coord, null); final double x = pixel.getX(); final double y = pixel.getY(); if (!Double.isNaN(x) && !Double.isNaN(y)) { dest = interpolate(x, y, dest, 0, image.getNumBands()); if (dest != null) { return dest; } } } catch (TransformException exception) { throw new CannotEvaluateException(/*coord*/ null, exception); // TODO } throw new PointOutsideCoverageException(coord); } /** * Return an sequence of double values for a given two-dimensional point in the coverage. * * @param coord The coordinate point where to evaluate. * @param dest An array in which to store values, or null. * @return An array containing values. * @throws CannotEvaluateException if the values can't be computed at the specified coordinate. * More specifically, {@link PointOutsideCoverageException} is thrown if the evaluation * failed because the input point has invalid coordinates. */ public double[] evaluate(final Point2D coord, double[] dest) throws CannotEvaluateException { if (fallback!=null) { dest = super.evaluate(coord, dest); } try { final Point2D pixel = toGrid.transform(coord, null); final double x = pixel.getX(); final double y = pixel.getY(); if (!Double.isNaN(x) && !Double.isNaN(y)) { dest = interpolate(x, y, dest, 0, image.getNumBands()); if (dest != null) { return dest; } } } catch (TransformException exception) { throw new CannotEvaluateException(/*coord*/ null, exception); // TODO } throw new PointOutsideCoverageException(coord); } /** * Interpolate at the specified position. If fallback!=null, * then dest must have been initialized with * super.evaluate(...) prior to invoking this method. * * @param x The x position in pixel's coordinates. * @param y The y position in pixel's coordinates. * @param dest The destination array, or null. * @param band The first band's index to interpolate. * @param bandUp The last band's index+1 to interpolate. * @return null if point is outside grid coverage. */ private synchronized int[] interpolate(final double x, final double y, int[] dest, int band, final int bandUp) { final double x0 = Math.floor(x); final double y0 = Math.floor(y); final int ix = (int)x0; final int iy = (int)y0; if (!(ix>=xmin && ix=ymin && iyfallback!=null, * then dest must have been initialized with * super.evaluate(...) prior to invoking this method. * * @param x The x position in pixel's coordinates. * @param y The y position in pixel's coordinates. * @param dest The destination array, or null. * @param band The first band's index to interpolate. * @param bandUp The last band's index+1 to interpolate. * @return null if point is outside grid coverage. */ private synchronized float[] interpolate(final double x, final double y, float[] dest, int band, final int bandUp) { final double x0 = Math.floor(x); final double y0 = Math.floor(y); final int ix = (int)x0; final int iy = (int)y0; if (!(ix>=xmin && ix=ymin && iyfallback!=null, * then dest must have been initialized with * super.evaluate(...) prior to invoking this method. * * @param x The x position in pixel's coordinates. * @param y The y position in pixel's coordinates. * @param dest The destination array, or null. * @param band The first band's index to interpolate. * @param bandUp The last band's index+1 to interpolate. * @return null if point is outside grid coverage. */ private synchronized double[] interpolate(final double x, final double y, double[] dest, int band, final int bandUp) { final double x0 = Math.floor(x); final double y0 = Math.floor(y); final int ix = (int)x0; final int iy = (int)y0; if (!(ix>=xmin && ix=ymin && iy