org.esa.beam.framework.datamodel.TiePointGrid.java Source code

Java tutorial

Introduction

Here is the source code for org.esa.beam.framework.datamodel.TiePointGrid.java

Source

/*
 * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option)
 * any later version.
 * This program 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 General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see http://www.gnu.org/licenses/
 */

package org.esa.beam.framework.datamodel;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.glevel.MultiLevelImage;
import com.bc.ceres.glevel.MultiLevelModel;
import com.bc.ceres.glevel.support.AbstractMultiLevelSource;
import com.bc.ceres.glevel.support.DefaultMultiLevelImage;
import org.esa.beam.framework.dataio.ProductSubsetDef;
import org.esa.beam.jai.ImageManager;
import org.esa.beam.jai.ResolutionLevel;
import org.esa.beam.jai.TiePointGridOpImage;
import org.esa.beam.util.Guardian;
import org.esa.beam.util.math.IndexValidator;
import org.esa.beam.util.math.MathUtils;
import org.esa.beam.util.math.Range;
import org.apache.commons.math.util.FastMath;

import java.awt.image.RenderedImage;
import java.io.IOException;

import Jama.Matrix;

/**
 * A tie-point grid contains the data for geophysical parameter in remote sensing data products. Tie-point grid are
 * two-dimensional images which hold their pixel values (samples) in a <code>float</code> array. <p/>
 * <p/>
 * Usually, tie-point grids are a sub-sampling of a data product's scene resolution.
 *
 * @author Norman Fomferra
    
 */
public class TiePointGrid extends RasterDataNode {

    /**
     * Tie point values are assumed to have none discontinuities.
     */
    public static int DISCONT_NONE = 0;

    /**
     * Tie point values have angles in the range -180...+180 degrees and may comprise a discontinuity at 180 (resp.
     * -180) degrees.
     */
    public static int DISCONT_AT_180 = 180;

    /**
     * Tie point values have are angles in the range 0...+360 degrees and may comprise a discontinuity at 360 (resp. 0)
     * degrees.
     */
    public static int DISCONT_AT_360 = 360;

    private final float offsetX;
    private final float offsetY;
    private final float subSamplingX;
    private final float subSamplingY;
    private final int rasterWidth;
    private final int rasterWidthMinus2;
    private final int rasterHeightMinus2;

    private int discontinuity;
    private TiePointGrid sinGrid;
    private TiePointGrid cosGrid;
    private float[] tiePoints = null;

    private double[][] quadraticInterpCoeffs = null; // 2 order quadratic polynomial coefficients
    private double[] biquadraticInterpCoeffs = null; // 2 order biquadratic polynomial coefficients

    public enum InterpMode {
        BILINEAR, QUADRATIC, BIQUADRATIC
    }

    /**
     * Constructs a new <code>TiePointGrid</code> with the given tie point grid properties.
     *
     * @param name         the name of the new object
     * @param gridWidth    the width of the tie-point grid in pixels
     * @param gridHeight   the height of the tie-point grid in pixels
     * @param offsetX      the X co-ordinate of the first (upper-left) tie-point in pixels
     * @param offsetY      the Y co-ordinate of the first (upper-left) tie-point in pixels
     * @param subSamplingX the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                     this tie-pint grid belongs to. Must not be less than one.
     * @param subSamplingY the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                     this tie-pint grid belongs to. Must not be less than one.
     * @param tiePoints    the tie-point data values, must be an array of the size <code>gridWidth * gridHeight</code>
     */
    public TiePointGrid(String name, int gridWidth, int gridHeight, float offsetX, float offsetY,
            float subSamplingX, float subSamplingY, float[] tiePoints) {
        this(name, gridWidth, gridHeight, offsetX, offsetY, subSamplingX, subSamplingY, tiePoints, DISCONT_NONE);
    }

    /**
     * Constructs a new <code>TiePointGrid</code> with the given tie point grid properties.
     *
     * @param name          the name of the new object
     * @param gridWidth     the width of the tie-point grid in pixels
     * @param gridHeight    the height of the tie-point grid in pixels
     * @param offsetX       the X co-ordinate of the first (upper-left) tie-point in pixels
     * @param offsetY       the Y co-ordinate of the first (upper-left) tie-point in pixels
     * @param subSamplingX  the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                      this tie-pint grid belongs to. Must not be less than one.
     * @param subSamplingY  the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                      this tie-pint grid belongs to. Must not be less than one.
     * @param tiePoints     the tie-point data values, must be an array of the size <code>gridWidth * gridHeight</code>
     * @param discontinuity the discontinuity mode, can be either {@link #DISCONT_NONE} or {@link #DISCONT_AT_180}
     *                      {@link #DISCONT_AT_360}
     */
    public TiePointGrid(String name, int gridWidth, int gridHeight, float offsetX, float offsetY,
            float subSamplingX, float subSamplingY, float[] tiePoints, int discontinuity) {
        super(name, ProductData.TYPE_FLOAT32, gridWidth, gridHeight);
        Guardian.assertNotNull("tiePoints", tiePoints);

        if (discontinuity != DISCONT_NONE && discontinuity != DISCONT_AT_180 && discontinuity != DISCONT_AT_360) {
            throw new IllegalArgumentException("unsupported discontinuity mode");
        }
        this.discontinuity = discontinuity;

        if (tiePoints.length != gridWidth * gridHeight) {
            throw new IllegalArgumentException("data array size does not match 'gridWidth' x 'gridHeight'");
        }
        if (subSamplingX <= 0.0F) {
            throw new IllegalArgumentException("'subSamplingX' is less or equal zero");
        }
        if (subSamplingY <= 0.0F) {
            throw new IllegalArgumentException("'subSamplingY' is less or equal zero");
        }
        this.offsetX = offsetX;
        this.offsetY = offsetY;
        this.subSamplingX = subSamplingX;
        this.subSamplingY = subSamplingY;
        rasterWidth = gridWidth;
        rasterWidthMinus2 = gridWidth - 2;
        rasterHeightMinus2 = gridHeight - 2;

        setData(ProductData.createInstance(tiePoints));

        prefetchTiePoints();
    }

    /**
     * Constructs a new <code>TiePointGrid</code> with the given tie point grid properties.
     *
     * @param name           the name of the new object
     * @param gridWidth      the width of the tie-point grid in pixels
     * @param gridHeight     the height of the tie-point grid in pixels
     * @param offsetX        the X co-ordinate of the first (upper-left) tie-point in pixels
     * @param offsetY        the Y co-ordinate of the first (upper-left) tie-point in pixels
     * @param subSamplingX   the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                       this tie-pint grid belongs to. Must not be less than one.
     * @param subSamplingY   the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which
     *                       this tie-pint grid belongs to. Must not be less than one.
     * @param tiePoints      the tie-point data values, must be an array of the size <code>gridWidth * gridHeight</code>
     * @param containsAngles if true, the {@link #getDiscontinuity() angular discontinuity} is derived from the provided tie-point data values
     */
    public TiePointGrid(String name, int gridWidth, int gridHeight, float offsetX, float offsetY,
            float subSamplingX, float subSamplingY, float[] tiePoints, boolean containsAngles) {
        this(name, gridWidth, gridHeight, offsetX, offsetY, subSamplingX, subSamplingY, tiePoints,
                containsAngles ? getDiscontinuity(tiePoints) : DISCONT_NONE);
    }

    /**
     * Determines the angular discontinuity of the given tie point values.
     *
     * @return the angular discontinuity, will always be either {@link #DISCONT_AT_180} or
     *         {@link #DISCONT_AT_360}
     */
    public static int getDiscontinuity(float[] tiePoints) {
        final Range range = Range.computeRangeFloat(tiePoints, IndexValidator.TRUE, null, ProgressMonitor.NULL);
        if (range.getMax() > 180.0) {
            return DISCONT_AT_360;
        } else {
            return DISCONT_AT_180;
        }
    }

    /**
     * Gets the angular discontinuity.
     *
     * @return the angular discontinuity, will always be either {@link #DISCONT_NONE} or {@link #DISCONT_AT_180} or
     *         {@link #DISCONT_AT_360}
     */
    public int getDiscontinuity() {
        return discontinuity;
    }

    /**
     * Sets the angular discontinuity.
     *
     * @param discontinuity angular discontinuity, can be either {@link #DISCONT_NONE} or {@link #DISCONT_AT_180} or
     *                      {@link #DISCONT_AT_360}
     */
    public void setDiscontinuity(final int discontinuity) {
        if (discontinuity != DISCONT_NONE && discontinuity != DISCONT_AT_180 && discontinuity != DISCONT_AT_360) {
            throw new IllegalArgumentException("unsupported discontinuity mode");
        }
        this.discontinuity = discontinuity;
    }

    /**
     * Returns <code>true</code>
     *
     * @return true
     */
    @Override
    public boolean isFloatingPointType() {
        return true;
    }

    /**
     * Returns the geophysical data type of this <code>RasterDataNode</code>. The value retuned is always one of the
     * <code>ProductData.TYPE_XXX</code> constants.
     *
     * @return the geophysical data type
     *
     * @see ProductData
     */
    @Override
    public int getGeophysicalDataType() {
        return ProductData.TYPE_FLOAT32;
    }

    /**
     * Gets a raster data holding this tie-point's interpolated pixel data for an entire product scene. <p/>
     * <p/>
     * In opposite to the <code>getRasterData</code> method, this method returns raster data that has at least
     * <code>getBandOutputRasterWidth()*getBandOutputRasterHeight()</code> elements of the given data type to store
     * the scene's pixels.
     *
     * @return raster data covering the pixels for a complete scene
     *
     * @see #getRasterData
     * @see #getRasterWidth
     * @see #getRasterHeight
     * @see #getSceneRasterWidth
     * @see #getSceneRasterHeight
     */
    @Override
    public ProductData getSceneRasterData() {
        int width = getSceneRasterWidth();
        int height = getSceneRasterHeight();
        ProductData data = createCompatibleRasterData(width, height);
        final float[] elems = (float[]) data.getElems();
        // getPixels will interpolate between tie points
        getPixels(0, 0, width, height, elems, ProgressMonitor.NULL);
        return data;
    }

    /**
     * Returns the width in pixels of the scene represented by this tie-point grid. The value returned is
     * <code>(getRasterWidth() - 1) * getSubSamplingX() + 1</code>
     *
     * @return the scene width in pixels
     */
    @Override
    public int getSceneRasterWidth() {
        if (getProduct() != null) {
            return getProduct().getSceneRasterWidth();
        } else {
            return Math.round((getRasterWidth() - 1) * getSubSamplingX() + 1);
        }
    }

    /**
     * Returns the height in pixels of the scene represented by this tie-point grid. The value returned is
     * <code>(getRasterHeight() - 1) * getSubSamplingY() + 1</code>
     *
     * @return the scene height in pixels
     */
    @Override
    public int getSceneRasterHeight() {
        if (getProduct() != null) {
            return getProduct().getSceneRasterHeight();
        } else {
            return Math.round((getRasterHeight() - 1) * getSubSamplingY() + 1);
        }
    }

    /**
     * Retrieves the x co-ordinate of the first (upper-left) tie-point in pixels.
     */
    public float getOffsetX() {
        return offsetX;
    }

    /**
     * Retrieves the y co-ordinate of the first (upper-left) tie-point in pixels.
     */
    public float getOffsetY() {
        return offsetY;
    }

    /**
     * Returns the sub-sampling in X-direction given in the pixel co-ordinates of the data product to which this
     * tie-pint grid belongs to.
     *
     * @return the sub-sampling in X-direction, never less than one.
     */
    public float getSubSamplingX() {
        return subSamplingX;
    }

    /**
     * Returns the sub-sampling in Y-direction given in the pixel co-ordinates of the data product to which this
     * tie-pint grid belongs to.
     *
     * @return the sub-sampling in Y-direction, never less than one.
     */
    public float getSubSamplingY() {
        return subSamplingY;
    }

    /**
     * Gets the data array holding this band's pixel samples.
     *
     * @return the data array for this band, or <code>null</code> if no data has been loaded
     *
     * @see ProductData#getElems
     */
    public float[] getTiePoints() {
        return (float[]) getRasterData().getElems();
    }

    /**
     * Gets the interpolated sample for the pixel located at (x,y) as an integer value. <p/>
     * <p/>
     * If the pixel co-odinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location
     * @param y The Y co-ordinate of the pixel location
     *
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    @Override
    public int getPixelInt(int x, int y) {
        return Math.round(getPixelFloat(x, y));
    }

    @Override
    public void dispose() {
        if (cosGrid != null) {
            cosGrid.dispose();
            cosGrid = null;
        }
        if (sinGrid != null) {
            sinGrid.dispose();
            sinGrid = null;
        }
        super.dispose();
    }

    /**
     * Computes the interpolated sample for the pixel located at (x,y). <p/>
     * <p/>
     * If the pixel co-odinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param y The Y co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     *
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    @Override
    public final float getPixelFloat(final int x, final int y) {
        return getPixelFloat(x + 0.5f, y + 0.5f);
    }

    /**
     * Computes the interpolated sample for the pixel located at (x,y) given as floating point co-ordinates. <p/>
     * <p/>
     * If the pixel co-odinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param y The Y co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     *
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    public final float getPixelFloat(final float x, final float y) {
        if (discontinuity != DISCONT_NONE) {
            if (isDiscontNotInit()) {
                initDiscont();
            }
            final float v = (float) (MathUtils.RTOD
                    * Math.atan2(sinGrid.getPixelFloat(x, y), cosGrid.getPixelFloat(x, y)));
            return (v < 0.0 && discontinuity == DISCONT_AT_360) ? 360.0F + v : v; // = 180 + (180 - abs(v))
        }
        final float fi = (x - offsetX) / subSamplingX;
        final float fj = (y - offsetY) / subSamplingY;
        final int i = MathUtils.crop((int) fi, 0, rasterWidthMinus2);
        final int j = MathUtils.crop((int) fj, 0, rasterHeightMinus2);
        return interpolate(fi - i, fj - j, i, j);
    }

    public final float getPixelFloat(final InterpInput in) {
        if (discontinuity != DISCONT_NONE) {
            if (isDiscontNotInit()) {
                initDiscont();
            }
            final float v = (float) (MathUtils.RTOD
                    * Math.atan2(sinGrid.getPixelFloat(in), cosGrid.getPixelFloat(in)));
            return (v < 0.0 && discontinuity == DISCONT_AT_360) ? 360.0F + v : v; // = 180 + (180 - abs(v))
        }
        return interpolate(in.wi, in.wj, in.i0, in.j0);
    }

    public InterpInput calcInterp(final float x, final float y) {
        final float fi = (x - offsetX) / subSamplingX;
        final float fj = (y - offsetY) / subSamplingY;
        final int i = MathUtils.crop((int) StrictMath.floor(fi), 0, rasterWidthMinus2);
        final int j = MathUtils.crop((int) StrictMath.floor(fj), 0, rasterHeightMinus2);
        return new InterpInput(fi - i, fj - j, i, j);
    }

    /**
     * Gets the interpolated sample for the pixel located at (x,y) as a double value. <p/>
     * <p/>
     * If the pixel co-ordinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param y The Y co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     *
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    @Override
    public double getPixelDouble(int x, int y) {
        return getPixelFloat(x, y);
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixelInt(int x, int y, int pixelValue) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixelFloat(int x, int y, float pixelValue) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixelDouble(int x, int y, double pixelValue) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as integer array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the integer array to be filled with data
     * @param pm     a monitor to inform the user about progress
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public short[] getPixels(int x, int y, int w, int h, short[] pixels, ProgressMonitor pm) {
        pixels = ensureMinLengthArray(pixels, w * h);
        final float[] fpixels = getPixels(x, y, w, h, (float[]) null, pm);
        for (int i = 0; i < fpixels.length; i++) {
            pixels[i] = (short) Math.round(fpixels[i]);
        }
        return pixels;
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as integer array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the integer array to be filled with data
     * @param pm     a monitor to inform the user about progress
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public int[] getPixels(int x, int y, int w, int h, int[] pixels, ProgressMonitor pm) {
        pixels = ensureMinLengthArray(pixels, w * h);
        final float[] fpixels = getPixels(x, y, w, h, (float[]) null, pm);
        for (int i = 0; i < fpixels.length; i++) {
            pixels[i] = Math.round(fpixels[i]);
        }
        return pixels;
    }

    /**
     * Retrieves an array of tie point data interpolated to the product width and height as float array. If the given
     * array is <code>null</code> a new one is created and returned.
     *
     * @param x1      the x coordinate of the array to be read
     * @param y1      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the float array to be filled with data
     * @param pm     a monitor to inform the user about progress
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public float[] getPixels(int x1, int y1, int w, int h, float[] pixels, ProgressMonitor pm) {
        pixels = ensureMinLengthArray(pixels, w * h);
        final int x2 = x1 + w;
        final int y2 = y1 + h;
        if (discontinuity != DISCONT_NONE) {
            if (isDiscontNotInit()) {
                initDiscont();
            }
            /*  int i = 0;
              for (int y = y1; y < y2; ++y) {
            for (int x = x1; x < x2; ++x) {
                //pixels[i++] = getPixelFloat(x + 0.5f, y + 0.5f);
                
                final float v = (float) (MathUtils.RTOD * Math.atan2(sinGrid.getPixelFloat(x+ 0.5f, y+ 0.5f),
                                                                     cosGrid.getPixelFloat(x+ 0.5f, y+ 0.5f)));
                pixels[i++] = (v < 0.0 && discontinuity == DISCONT_AT_360) ? 360.0F + v : v;
            }
              }  */
            float[] sinPixels = new float[pixels.length];
            float[] cosPixels = new float[pixels.length];
            sinGrid.getPixels(x1, y1, w, h, sinPixels, pm);
            cosGrid.getPixels(x1, y1, w, h, cosPixels, pm);
            int j = 0;
            for (float sin : sinPixels) {
                final float v = (float) (MathUtils.RTOD * Math.atan2(sin, cosPixels[j]));
                pixels[j++] = (v < 0.0 && discontinuity == DISCONT_AT_360) ? 360.0F + v : v;
            }
        } else {
            final float x0 = 0.5f - offsetX;
            final float y0 = 0.5f - offsetY;
            int i, j;
            float fi, fj;
            float wj;
            int i1, j1, jrw, j1rw;

            float subSamplingY = 1f / this.subSamplingY;
            float subSamplingX = 1f / this.subSamplingX;
            int pos = 0;

            for (int y = y1; y < y2; ++y) {
                fj = (y + y0) * subSamplingY;
                j = MathUtils.crop((int) fj, 0, rasterHeightMinus2);

                wj = fj - j;
                j1 = j + 1;
                jrw = j * rasterWidth;
                j1rw = j1 * rasterWidth;
                for (int x = x1; x < x2; ++x) {
                    fi = (x + x0) * subSamplingX;
                    i = (int) fi < 0 ? 0 : (int) fi > rasterWidthMinus2 ? rasterWidthMinus2 : (int) fi;

                    i1 = i + 1;
                    pixels[pos++] = MathUtils.interpolate2D(fi - i, wj, tiePoints[i + jrw], tiePoints[i1 + jrw],
                            tiePoints[i + j1rw], tiePoints[i1 + j1rw]);
                }
            }
        }
        return pixels;
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as double array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the double array to be filled with data
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public double[] getPixels(int x, int y, int w, int h, double[] pixels, ProgressMonitor pm) {
        pixels = ensureMinLengthArray(pixels, w * h);
        final float[] fpixels = getPixels(x, y, w, h, (float[]) null, pm);
        for (int i = 0; i < fpixels.length; i++) {
            pixels[i] = fpixels[i];
        }
        return pixels;
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixels(int x, int y, int w, int h, int[] pixels) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixels(int x, int y, int w, int h, float[] pixels) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void setPixels(int x, int y, int w, int h, double[] pixels) {
        raisePixelsAreReadOnlyError();
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as float array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the integer array to be filled with data
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public short[] readPixels(int x, int y, int w, int h, short[] pixels, ProgressMonitor pm) throws IOException {
        return getPixels(x, y, w, h, pixels, pm);
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as float array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the integer array to be filled with data
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public int[] readPixels(int x, int y, int w, int h, int[] pixels, ProgressMonitor pm) throws IOException {
        return getPixels(x, y, w, h, pixels, pm);
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as float array. If the given
     * array is <code>null</code> a new one was created and returned. *
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the float array to be filled with data
     * @param pm     a monitor to inform the user about progress
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public float[] readPixels(int x, int y, int w, int h, float[] pixels, ProgressMonitor pm) throws IOException {
        return getPixels(x, y, w, h, pixels, pm);
    }

    /**
     * Retrieves an array of tie point data interpolated to the product with and height as double array. If the given
     * array is <code>null</code> a new one was created and returned.
     *
     * @param x      the x coordinate of the array to be read
     * @param y      the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the double array to be filled with data
     * @param pm     a monitor to inform the user about progress
     *
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    @Override
    public double[] readPixels(int x, int y, int w, int h, double[] pixels, ProgressMonitor pm) throws IOException {
        return getPixels(x, y, w, h, pixels, pm);
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void writePixels(int x, int y, int w, int h, int[] pixels, ProgressMonitor pm) throws IOException {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void writePixels(int x, int y, int w, int h, float[] pixels, ProgressMonitor pm) throws IOException {
        raisePixelsAreReadOnlyError();
    }

    /**
     * This method is not implemented because pixels are read-only in tie-point grids.
     */
    @Override
    public void writePixels(int x, int y, int w, int h, double[] pixels, ProgressMonitor pm) throws IOException {
        raisePixelsAreReadOnlyError();
    }

    /**
     * Reads raster data from this dataset into the user-supplied raster data buffer. <p/>
     * <p/>
     * This method always directly (re-)reads this band's data from its associated data source into the given data
     * buffer.
     *
     * @param offsetX    the X-offset in the raster co-ordinates where reading starts
     * @param offsetY    the Y-offset in the raster co-ordinates where reading starts
     * @param width      the width of the raster data buffer
     * @param height     the height of the raster data buffer
     * @param rasterData a raster data buffer receiving the pixels to be read
     * @param pm         a monitor to inform the user about progress
     *
     * @throws java.io.IOException      if an I/O error occurs
     * @throws IllegalArgumentException if the raster is null
     * @throws IllegalStateException    if this product raster was not added to a product so far, or if the product to
     *                                  which this product raster belongs to, has no associated product reader
     * @see org.esa.beam.framework.dataio.ProductReader#readBandRasterData(Band, int, int, int, int, ProductData, com.bc.ceres.core.ProgressMonitor) 
     */
    @Override
    public void readRasterData(int offsetX, int offsetY, int width, int height, ProductData rasterData,
            ProgressMonitor pm) throws IOException {
        final ProductData src = getRasterData();
        int iSrc;
        int iDest = 0;
        pm.beginTask("Reading raster data...", height);
        try {
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    iSrc = (offsetY + y) * width + (offsetX + x);
                    rasterData.setElemDoubleAt(iDest, src.getElemDoubleAt(iSrc));
                    iDest++;
                }
                pm.worked(1);
            }
        } finally {
            pm.done();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void readRasterDataFully(ProgressMonitor pm) throws IOException {
        // ok, raster data is already loaded in tie-point grids
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeRasterData(int offsetX, int offsetY, int width, int height, ProductData rasterData,
            ProgressMonitor pm) throws IOException {
        raisePixelsAreReadOnlyError();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeRasterDataFully(ProgressMonitor pm) throws IOException {
        raisePixelsAreReadOnlyError();
    }

    @Override
    protected RenderedImage createSourceImage() {
        final MultiLevelModel model = ImageManager.getInstance().getMultiLevelModel(this);
        MultiLevelImage multiLevelImage = new DefaultMultiLevelImage(new AbstractMultiLevelSource(model) {

            @Override
            public RenderedImage createImage(int level) {
                return new TiePointGridOpImage(TiePointGrid.this, ResolutionLevel.create(getModel(), level));
            }
        });
        return multiLevelImage;
    }

    // ////////////////////////////////////////////////////////////////////////
    // 'Visitor' pattern support

    /**
     * Accepts the given visitor. This method implements the well known 'Visitor' design pattern of the gang-of-four.
     * The visitor pattern allows to define new operations on the product data model without the need to add more code
     * to it. The new operation is implemented by the visitor. <p/>
     * <p/>
     * The method simply calls <code>visitor.visit(this)</code>.
     *
     * @param visitor the visitor
     */
    @Override
    public void acceptVisitor(ProductVisitor visitor) {
        Guardian.assertNotNull("visitor", visitor);
        visitor.visit(this);
    }

    public TiePointGrid cloneTiePointGrid() {
        final float[] srcTiePoints = this.getTiePoints();
        final float[] destTiePoints = new float[srcTiePoints.length];
        System.arraycopy(srcTiePoints, 0, destTiePoints, 0, srcTiePoints.length);
        TiePointGrid clone = new TiePointGrid(this.getName(), this.getRasterWidth(), this.getRasterHeight(),
                this.getOffsetX(), this.getOffsetY(), this.getSubSamplingX(), this.getSubSamplingY(), destTiePoints,
                this.getDiscontinuity());
        clone.setUnit(getUnit());
        clone.setDescription(getDescription());
        return clone;

    }

    // ////////////////////////////////////////////////////////////////////////
    // Public static helpers

    public static TiePointGrid createZenithFromElevationAngleTiePointGrid(TiePointGrid elevationAngleGrid) {
        final float[] elevationAngles = elevationAngleGrid.getTiePoints();
        final float[] zenithAngles = new float[elevationAngles.length];
        for (int i = 0; i < zenithAngles.length; i++) {
            zenithAngles[i] = 90.0f - elevationAngles[i];
        }
        return new TiePointGrid(elevationAngleGrid.getName(), elevationAngleGrid.getRasterWidth(),
                elevationAngleGrid.getRasterHeight(), elevationAngleGrid.getOffsetX(),
                elevationAngleGrid.getOffsetY(), elevationAngleGrid.getSubSamplingX(),
                elevationAngleGrid.getSubSamplingY(), zenithAngles);
    }

    // ////////////////////////////////////////////////////////////////////////
    // Implementation helpers

    private static void raisePixelsAreReadOnlyError() {
        throw new IllegalStateException("pixels are read-only in tie-point grids");
    }

    /**
     * If calling interpolate from within a loop
     * it will be faster to prefetch the tiepoints once outside of your loop
     */
    public void prefetchTiePoints() {
        tiePoints = getTiePoints();
    }

    private float interpolate(float wi, float wj, int i0, int j0) {
        final int i1 = i0 + 1;
        final int j1 = j0 + 1;
        return MathUtils.interpolate2D(wi, wj, tiePoints[i0 + j0 * rasterWidth], tiePoints[i1 + j0 * rasterWidth],
                tiePoints[i0 + j1 * rasterWidth], tiePoints[i1 + j1 * rasterWidth]);

    }

    private boolean isDiscontNotInit() {
        return sinGrid == null;
    }

    private synchronized void initDiscont() {
        TiePointGrid base = this;
        final float[] tiePoints = base.getTiePoints();
        final float[] sinTiePoints = new float[tiePoints.length];
        final float[] cosTiePoints = new float[tiePoints.length];
        for (int i = 0; i < tiePoints.length; i++) {
            float tiePoint = tiePoints[i];
            sinTiePoints[i] = (float) FastMath.sin(MathUtils.DTOR * tiePoint);
            cosTiePoints[i] = (float) FastMath.cos(MathUtils.DTOR * tiePoint);
        }
        sinGrid = new TiePointGrid(base.getName(), base.getRasterWidth(), base.getRasterHeight(), base.getOffsetX(),
                base.getOffsetY(), base.getSubSamplingX(), base.getSubSamplingY(), sinTiePoints);
        cosGrid = new TiePointGrid(base.getName(), base.getRasterWidth(), base.getRasterHeight(), base.getOffsetX(),
                base.getOffsetY(), base.getSubSamplingX(), base.getSubSamplingY(), cosTiePoints);
    }

    protected static short[] ensureMinLengthArray(short[] array, int length) {
        if (array == null) {
            return new short[length];
        }
        if (array.length < length) {
            throw new IllegalArgumentException("The length of the given array is less than " + length);
        }
        return array;
    }

    protected static int[] ensureMinLengthArray(int[] array, int length) {
        if (array == null) {
            return new int[length];
        }
        if (array.length < length) {
            throw new IllegalArgumentException("The length of the given array is less than " + length);
        }
        return array;
    }

    protected static float[] ensureMinLengthArray(float[] array, int length) {
        if (array == null) {
            return new float[length];
        }
        if (array.length < length) {
            throw new IllegalArgumentException("The length of the given array is less than " + length);
        }
        return array;
    }

    protected static double[] ensureMinLengthArray(double[] array, int length) {
        if (array == null) {
            return new double[length];
        }
        if (array.length < length) {
            throw new IllegalArgumentException("The length of the given array is less than " + length);
        }
        return array;
    }

    private static int convertCycleToDiscont(final float cycleMin, final float cycleMax) {
        int discontinuity = DISCONT_NONE;
        if (cycleMin == -180.0f && cycleMax == 180.0f) {
            discontinuity = DISCONT_AT_180;
        } else if (cycleMin == 0.0f && cycleMax == 360.0f) {
            discontinuity = DISCONT_AT_360;
        } else if (cycleMin != cycleMax) {
            throw new IllegalArgumentException("unsupported discontinuity mode");
        }
        return discontinuity;
    }

    public static TiePointGrid createSubset(TiePointGrid sourceTiePointGrid, ProductSubsetDef subsetDef) {
        final int srcTPGRasterWidth = sourceTiePointGrid.getRasterWidth();
        final int srcTPGRasterHeight = sourceTiePointGrid.getRasterHeight();
        final float srcTPGSubSamplingX = sourceTiePointGrid.getSubSamplingX();
        final float srcTPGSubSamplingY = sourceTiePointGrid.getSubSamplingY();
        int subsetOffsetX = 0;
        int subsetOffsetY = 0;
        int subsetStepX = 1;
        int subsetStepY = 1;
        final int srcSceneRasterWidth = sourceTiePointGrid.getSceneRasterWidth();
        final int srcSceneRasterHeight = sourceTiePointGrid.getSceneRasterHeight();
        int subsetWidth = srcSceneRasterWidth;
        int subsetHeight = srcSceneRasterHeight;
        if (subsetDef != null) {
            subsetStepX = subsetDef.getSubSamplingX();
            subsetStepY = subsetDef.getSubSamplingY();
            if (subsetDef.getRegion() != null) {
                subsetOffsetX = subsetDef.getRegion().x;
                subsetOffsetY = subsetDef.getRegion().y;
                subsetWidth = subsetDef.getRegion().width;
                subsetHeight = subsetDef.getRegion().height;
            }
        }

        final float newTPGSubSamplingX = srcTPGSubSamplingX / subsetStepX;
        final float newTPGSubSamplingY = srcTPGSubSamplingY / subsetStepY;
        final float pixelCenter = 0.5f;
        final float newTPGOffsetX = (sourceTiePointGrid.getOffsetX() - pixelCenter - subsetOffsetX) / subsetStepX
                + pixelCenter;
        final float newTPGOffsetY = (sourceTiePointGrid.getOffsetY() - pixelCenter - subsetOffsetY) / subsetStepY
                + pixelCenter;
        final float newOffsetX = newTPGOffsetX % newTPGSubSamplingX;
        final float newOffsetY = newTPGOffsetY % newTPGSubSamplingY;
        final float diffX = newOffsetX - newTPGOffsetX;
        final float diffY = newOffsetY - newTPGOffsetY;
        final int dataOffsetX;
        if (diffX < 0.0f) {
            dataOffsetX = 0;
        } else {
            dataOffsetX = Math.round(diffX / newTPGSubSamplingX);
        }
        final int dataOffsetY;
        if (diffY < 0.0f) {
            dataOffsetY = 0;
        } else {
            dataOffsetY = Math.round(diffY / newTPGSubSamplingY);
        }

        int newTPGWidth = (int) Math.ceil(subsetWidth / srcTPGSubSamplingX) + 2;
        if (dataOffsetX + newTPGWidth > srcTPGRasterWidth) {
            newTPGWidth = srcTPGRasterWidth - dataOffsetX;
        }
        int newTPGHeight = (int) Math.ceil(subsetHeight / srcTPGSubSamplingY) + 2;
        if (dataOffsetY + newTPGHeight > srcTPGRasterHeight) {
            newTPGHeight = srcTPGRasterHeight - dataOffsetY;
        }

        final float[] oldTiePoints = sourceTiePointGrid.getTiePoints();
        final float[] tiePoints = new float[newTPGWidth * newTPGHeight];
        for (int y = 0; y < newTPGHeight; y++) {
            final int srcPos = srcTPGRasterWidth * (dataOffsetY + y) + dataOffsetX;
            System.arraycopy(oldTiePoints, srcPos, tiePoints, y * newTPGWidth, newTPGWidth);
        }

        final TiePointGrid tiePointGrid = new TiePointGrid(sourceTiePointGrid.getName(), newTPGWidth, newTPGHeight,
                newOffsetX, newOffsetY, newTPGSubSamplingX, newTPGSubSamplingY, tiePoints,
                sourceTiePointGrid.getDiscontinuity());
        tiePointGrid.setUnit(sourceTiePointGrid.getUnit());
        tiePointGrid.setDescription(sourceTiePointGrid.getDescription());
        return tiePointGrid;
    }

    /**=========================== Quadratic/Biquadratic Interpolations ================================**/
    /**
     * Compute polynomial coefficients for quadratic interpolation. For each tie point record, 3 coefficients
     * are computed. The 3 coefficients are saved in a row in _quadraticInterpCoeffs as {a0, a1, a2}. The
     * quadratic polynomial is given as f(x) = a0 + a1*x + a2*x^2.
     */
    private synchronized void computeQuadraticInterpCoeffs() {

        if (quadraticInterpCoeffs != null)
            return;

        final int numCoeff = 3;
        final int width = getRasterWidth();
        final int height = getRasterHeight();

        if (tiePoints == null) {
            tiePoints = getTiePoints();
        }

        final double[][] sampleIndexArray = new double[width][numCoeff];
        for (int c = 0; c < width; c++) {
            //            final int x = Math.min((int)(c*subSamplingX), getSceneRasterWidth() - 1);
            final int x = (int) (offsetX + c * subSamplingX);
            sampleIndexArray[c][0] = 1.0;
            sampleIndexArray[c][1] = (double) (x);
            sampleIndexArray[c][2] = (double) (x * x);
        }
        final Matrix A = new Matrix(sampleIndexArray);

        quadraticInterpCoeffs = new double[height][numCoeff];
        final double[] tiePointArray = new double[width];
        for (int r = 0; r < height; r++) {
            final int rwidth = r * width;
            for (int c = 0; c < width; c++) {
                tiePointArray[c] = (double) (tiePoints[rwidth + c]);
            }
            final Matrix b = new Matrix(tiePointArray, width);
            final Matrix x = A.solve(b);
            quadraticInterpCoeffs[r] = x.getColumnPackedCopy();
        }
    }

    /**
     * Compute polynomial coefficients for biquadratic interpolation. The 6 coefficients are given as
     * _biquadraticInterpCoeffs = {a0, a1, a2, a3, a4, a5} and the biquadratic polynomial is given as
     * f(x,y) = a0 + a1*x + a2*y + a3*x^2 + a4*y*x + a5*y^2.
     */
    private synchronized void computeBiquadraticInterpCoeffs() {

        if (biquadraticInterpCoeffs != null)
            return;

        final int numCoeff = 6;
        final int w = getRasterWidth();
        final int h = getRasterHeight();
        final int n = w * h;

        // prepare matrix A
        final double[][] sampleIndexArray = new double[n][numCoeff];
        for (int i = 0; i < h; i++) {
            final int y = (int) (i * subSamplingY);
            final double yy = y * y;
            final int iw = i * w;
            for (int j = 0; j < w; j++) {
                final int k = iw + j;
                //                final int x = Math.min((int)(j*subSamplingX), getSceneRasterWidth() - 1);
                //                final int y = Math.min((int)(i*subSamplingY), getSceneRasterHeight() - 1);
                final int x = (int) (j * subSamplingX);
                sampleIndexArray[k][0] = 1.0;
                sampleIndexArray[k][1] = (double) (x);
                sampleIndexArray[k][2] = (double) (y);
                sampleIndexArray[k][3] = (double) (x * x);
                sampleIndexArray[k][4] = (double) (y * x);
                sampleIndexArray[k][5] = yy;
            }
        }
        final Matrix A = new Matrix(sampleIndexArray);

        // prepare matrix b
        if (tiePoints == null) {
            tiePoints = getTiePoints();
        }
        final double[] tiePointArray = new double[n];
        for (int i = 0; i < n; i++) {
            tiePointArray[i] = (double) (tiePoints[i]);
        }
        final Matrix b = new Matrix(tiePointArray, n);

        // comoute coefficients
        final Matrix x = A.solve(b);
        biquadraticInterpCoeffs = x.getColumnPackedCopy();
    }

    /**
     * Computes the interpolated sample for the pixel located at (x,y) given as floating point co-ordinates. <p/>
     * <p/>
     * If the pixel co-odinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param y The Y co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param interpMethod String indicating the interpolation method.
     * @return The interpolated sample value.
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    public float getPixelFloat(float x, float y, InterpMode interpMethod) {

        if (interpMethod == InterpMode.BILINEAR) {

            return getPixelFloat(x, y);

        } else if (interpMethod == InterpMode.QUADRATIC) {

            if (quadraticInterpCoeffs == null) {
                computeQuadraticInterpCoeffs();
            }
            int r = (int) ((y - offsetY) / subSamplingY);
            if (r >= quadraticInterpCoeffs.length) {
                r = quadraticInterpCoeffs.length - 1;
            }
            return (float) (quadraticInterpCoeffs[r][0] + quadraticInterpCoeffs[r][1] * x
                    + quadraticInterpCoeffs[r][2] * x * x);

        } else if (interpMethod == InterpMode.BIQUADRATIC) {

            if (biquadraticInterpCoeffs == null) {
                computeBiquadraticInterpCoeffs();
            }
            return (float) (biquadraticInterpCoeffs[0] + biquadraticInterpCoeffs[1] * x
                    + biquadraticInterpCoeffs[2] * y + biquadraticInterpCoeffs[3] * x * x
                    + biquadraticInterpCoeffs[4] * x * y + biquadraticInterpCoeffs[5] * y * y);

        } else {
            throw new IllegalArgumentException("unsupported interpolation method");
        }
    }

    /**
     * Gets the interpolated sample for the pixel located at (x,y) as a double value. <p/>
     * <p/>
     * If the pixel co-ordinates given by (x,y) are not covered by this tie-point grid, the method extrapolates.
     *
     * @param x The X co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param y The Y co-ordinate of the pixel location, given in the pixel co-ordinates of the data product to which
     *          this tie-pint grid belongs to.
     * @param interpMethod String indicating the interpolation method.
     * @return The interpolated sample value.
     * @throws ArrayIndexOutOfBoundsException if the co-ordinates are not in bounds
     */
    public double getPixelDouble(float x, float y, InterpMode interpMethod) {
        return getPixelFloat(x, y, interpMethod);
    }

    /**
     * Retrieves an array of tie point data interpolated to the product width and height as float array. If the given
     * array is <code>null</code> a new one is created and returned.
     *
     * @param x0     the x coordinate of the array to be read
     * @param y0     the y coordinate of the array to be read
     * @param w      the width of the array to be read
     * @param h      the height of the array to be read
     * @param pixels the float array to be filled with data
     * @param pm     a monitor to inform the user about progress
     * @param interpMethod String indicating the interpolation method.
     * @return Array of interpolated sample values.
     * @throws IllegalArgumentException if the length of the given array is less than <code>w*h</code>.
     */
    public float[] getPixels(int x0, int y0, int w, int h, float[] pixels, ProgressMonitor pm,
            InterpMode interpMethod) {

        pixels = ensureMinLengthArray(pixels, w * h);
        if (interpMethod == InterpMode.BILINEAR) {

            return getPixels(x0, y0, w, h, pixels, pm);

        } else if (interpMethod == InterpMode.QUADRATIC || interpMethod == InterpMode.BIQUADRATIC) {

            int k = 0;
            final int maxY = y0 + h;
            final int maxX = x0 + w;
            for (int y = y0; y < maxY; y++) {
                for (int x = x0; x < maxX; x++) {
                    pixels[k++] = getPixelFloat(x, y, interpMethod);
                }
            }
            return pixels;

        } else {

            throw new IllegalArgumentException("unsupported interpolation method");
        }
    }

    public final static class InterpInput {
        final float wi;
        final float wj;
        final int i0;
        final int j0;

        InterpInput(float wi, float wj, int i0, int j0) {
            this.wi = wi;
            this.wj = wj;
            this.i0 = i0;
            this.j0 = j0;
        }
    }
}