true
if we should apply a conservative policy for the "piecewise" operation.
* The conservative policy is to apply "piecewise" only if there is no ambiguity about what
* the user wants.
*/
private static final boolean CONSERVATIVE_PIECEWISE = true;
/**
* Axis orientation of image's coordinate systems. In most images, x values are
* increasing toward the right (EAST
) and y values are increasing
* toward the bottom (SOUTH
). This is different to many geographic coordinate
* systems, which have y values increasing NORTH
. The grid coverage
* constructor will compare the geographic axis orientations to this
* IMAGE_ORIENTATION
and inverse the y axis if necessary. The axis
* inversions are handle by {@link GridGeometry#getGridToCoordinateSystem()}.
*/
private static final AxisOrientation[] IMAGE_ORIENTATION = {
AxisOrientation.EAST,
AxisOrientation.SOUTH
};
/**
* Pool of created object. Objects in this pool must be immutable.
* Those objects will be shared among many grid coverages.
*/
private static final WeakHashSet pool = new WeakHashSet();
/**
* An empty list of grid coverage.
*/
private static final GridCoverage[] EMPTY_LIST = new GridCoverage[0];
/**
* Sources grid coverage, or null
if none.
* This information is lost during serialization.
*/
private final transient GridCoverage[] sources;
/**
* A grid coverage using the sample dimensions SampleDimension.inverse
.
* This object is constructed and returned by {@link #geophysics}. Constructed when
* first needed. May appears also in the sources
list.
*/
private transient GridCoverage inverse;
/**
* The raster data.
*
* @todo This field should be final. It is not only for internal reason (namely:
* deserialization). Consider making this field private in a future version.
*/
protected transient PlanarImage image;
/**
* The serialized image, as an instance of {@link SerializableRenderedImage}.
* This image will be created only when first needed during serialization.
*/
private RenderedImage serializedImage;
/**
* The grid geometry.
*/
protected final GridGeometry gridGeometry;
/**
* The image's envelope. This envelope must have at least two
* dimensions. It may have more dimensions if the image have
* some extend in other dimensions (for example a depth, or
* a start and end time).
*/
private final Envelope envelope;
/**
* List of sample dimension information for the grid coverage.
* For a grid coverage, a sample dimension is a band. The sample dimension information
* include such things as description, data type of the value (bit, byte, integer...),
* the no data values, minimum and maximum values and a color table if one is associated
* with the dimension. A coverage must have at least one sample dimension.
*/
private final SampleDimension[] sampleDimensions;
/**
* true
is all sample in the image are geophysics values.
*/
private final boolean isGeophysics;
/**
* Construct a new grid coverage with the same parameter than the specified
* coverage. This constructor is useful when creating a coverage with
* identical data, but in which some method has been overriden in order to
* process data differently (e.g. interpolating them).
*
* @param coverage The source grid coverage.
*/
protected GridCoverage(final GridCoverage coverage) {
super(coverage);
image = coverage.image;
gridGeometry = coverage.gridGeometry;
envelope = coverage.envelope;
sampleDimensions = coverage.sampleDimensions;
isGeophysics = coverage.isGeophysics;
sources = new GridCoverage[] {coverage};
}
/**
* Construt a grid coverage from an image function.
*
* @param name The grid coverage name.
* @param function The image function.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods. The
* number of dimensions must matches the number of dimensions for
* the grid range in gridGeometry
.
* @param gridGeometry The grid geometry. The grid range must contains the expected
* image size (width and height).
* @param bands Sample dimensions for each image band, or null
for
* default sample dimensions. If non-null, then this array's length
* must matches the number of bands in image
.
* @param properties The set of properties for this coverage, or null
* if there is none. "Properties" in Java Advanced Imaging is what
* OpenGIS calls "Metadata". There is no getMetadataValue(...)
* method in this implementation. Use {@link #getProperty} instead. Keys may
* be {@link String} or {@link CaselessStringKey} objects, while values may
* be any {@link Object}.
*
* @throws MismatchedDimensionException If the grid range's dimension
* is not the same than the coordinate system's dimension.
*/
public GridCoverage(final String name, final ImageFunction function,
final CoordinateSystem cs, final GridGeometry gridGeometry,
final SampleDimension[] bands, final Map properties)
throws MismatchedDimensionException
{
this(name, getImage(function, gridGeometry),
cs, gridGeometry, null, bands, null, properties);
}
/**
* Create an image from an image function. Translation and scale
* factors are fetched from the grid geometry, which must have an
* affine transform.
*
* @task TODO: We could support shear in affine transform.
* @task TODO: Should be inlined in the above constructor if only Sun was to fix RFE #4093999
* ("Relax constraint on placement of this()/super() call in constructors").
*/
private static PlanarImage getImage(final ImageFunction function,
final GridGeometry gridGeometry)
{
final MathTransform transform = gridGeometry.getGridToCoordinateSystem2D();
if (!(transform instanceof AffineTransform)) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NOT_AN_AFFINE_TRANSFORM));
}
final AffineTransform at = (AffineTransform) transform;
if (at.getShearX()!=0 || at.getShearY()!=0) {
// TODO: We may support that in a future version.
// 1) Create a copy with shear[X/Y] set to 0. Use the copy.
// 2) Compute the residu with createInverse() and concatenate().
// 3) Apply the residu with JAI.create("Affine").
throw new IllegalArgumentException("Shear and rotation not supported");
}
final double xScale = at.getScaleX();
final double yScale = at.getScaleY();
final double xTrans = -at.getTranslateX()/xScale;
final double yTrans = -at.getTranslateY()/yScale;
final GridRange range = gridGeometry.getGridRange();
final ParameterBlock param = new ParameterBlock().add(function)
.add(range.getLength(0)) // width
.add(range.getLength(1)) // height
.add((float) xScale)
.add((float) yScale)
.add((float) xTrans)
.add((float) yTrans);
return JAI.create("ImageFunction", param);
}
/**
* Constructs a grid coverage from a raster and an envelope in
* longitude,latitude coordinates. The coordinate system is assumed to
* be based on {@linkplain GeographicCoordinateSystem#WGS84 WGS84}. A default color palette
* is built from the minimal and maximal values found in the raster.
*
* @param name The grid coverage name.
* @param raster The data (may be floating point numbers). {@linkplain Float#NaN NaN}
* values are mapped to a transparent color.
* @param envelope The envelope in geographic (longitude,latitude)
* coordinates.
*
* @throws MismatchedDimensionException If the envelope's dimension is not 2.
*/
public GridCoverage(final String name, final WritableRaster raster, final Envelope envelope)
throws MismatchedDimensionException
{
this(name, raster, GeographicCoordinateSystem.WGS84, envelope, null, null, null, null, null);
}
/**
* Constructs a grid coverage from a {@linkplain Raster raster} with the specified
* {@linkplain Envelope envelope}.
*
* @param name The grid coverage name.
* @param raster The data (may be floating point numbers). {@linkplain Float#NaN NaN}
* values are mapped to a transparent color.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods. The
* number of dimensions must matches the number of dimensions for
* envelope
.
* @param envelope The grid coverage cordinates. This envelope must have at least two
* dimensions. The two first dimensions describe the image location
* along x and y axis. The other dimensions are
* optional and may be used to locate the image on a vertical axis or
* on the time axis.
* @param minValues The minimal value for each bands in the raster, or null
* for computing it automatically.
* @param maxValues The maximal value for each bands in the raster, or null
* for computing it automatically.
* @param units The units of sample values, or null
if unknow.
* @param colors The colors to use for values from minValues
to
* maxValues
for each bands, or null
for a
* default color palette. If non-null, each arrays colors[b]
* may have any length; colors will be interpolated as needed.
* @param hints An optional set of rendering hints, or null
if none.
* Those hints will not affect the grid coverage to be created. However,
* they may affect the grid coverage to be returned by
* {@link #geophysics geophysics}(false)
, i.e.
* the view to be used at rendering time. The optional hint
* {@link org.geotools.gp.Hints#SAMPLE_DIMENSION_TYPE} specifies the
* {@link SampleDimensionType} to be used at rendering time, which can be
* one of {@link SampleDimensionType#UBYTE UBYTE} or
* {@link SampleDimensionType#USHORT USHORT}.
*
* @throws MismatchedDimensionException If the envelope's dimension
* is not the same than the coordinate system's dimension.
* @throws IllegalArgumentException if the number of bands differs
* from the number of sample dimensions.
*/
public GridCoverage(final String name, final WritableRaster raster,
final CoordinateSystem cs, final Envelope envelope,
final double[] minValues, final double[] maxValues,
final Unit units,
final Color[][] colors, final RenderingHints hints)
{
this(name, raster, cs, null, (Envelope)envelope.clone(),
GridSampleDimension.create(name, raster, minValues, maxValues, units, colors, hints));
}
/**
* Constructs a grid coverage from a {@linkplain Raster raster} with the specified
* "{@linkplain GridGeometry#getGridToCoordinateSystem grid to coordinate system}"
* transform.
*
* @param name The grid coverage name.
* @param raster The data (may be floating point numbers). {@linkplain Float#NaN NaN}
* values are mapped to a transparent color.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods.
* @param gridToCS The math transform from grid to coordinate system.
* @param minValues The minimal value for each bands in the raster, or null
* for computing it automatically.
* @param maxValues The maximal value for each bands in the raster, or null
* for computing it automatically.
* @param units The units of sample values, or null
if unknow.
* @param colors The colors to use for values from minValues
to
* maxValues
for each bands, or null
for a
* default color palette. If non-null, each arrays colors[b]
* may have any length; colors will be interpolated as needed.
* @param hints An optional set of rendering hints, or null
if none.
* Those hints will not affect the grid coverage to be created. However,
* they may affect the grid coverage to be returned by
* {@link #geophysics geophysics}(false)
, i.e.
* the view to be used at rendering time. The optional hint
* {@link org.geotools.gp.Hints#SAMPLE_DIMENSION_TYPE} specifies the
* {@link SampleDimensionType} to be used at rendering time, which can be
* one of {@link SampleDimensionType#UBYTE UBYTE} or
* {@link SampleDimensionType#USHORT USHORT}.
*
* @throws MismatchedDimensionException If the gridToCS
dimension
* is not the same than the coordinate system's dimension.
* @throws IllegalArgumentException if the number of bands differs
* from the number of sample dimensions.
*/
public GridCoverage(final String name, final WritableRaster raster,
final CoordinateSystem cs, final MathTransform gridToCS,
final double[] minValues, final double[] maxValues,
final Unit units,
final Color[][] colors, final RenderingHints hints)
{
this(name, raster, cs, new GridGeometry(null, gridToCS), null,
GridSampleDimension.create(name, raster, minValues, maxValues, units, colors, hints));
}
/**
* Helper constructor for public constructors expecting a {@link Raster} argument.
*
* @task TODO: Should be inlined in the above constructor if only Sun was to fix RFE #4093999
* ("Relax constraint on placement of this()/super() call in constructors").
*/
private GridCoverage(final String name,
final WritableRaster raster,
final CoordinateSystem cs,
GridGeometry gridGeometry, // ONE and only one of those
Envelope envelope, // two arguments should be non-null.
final SampleDimension[] bands)
{
this(name,
PlanarImage.wrapRenderedImage(
new BufferedImage(bands[0].getColorModel(0, bands.length), raster, false, null)),
cs, gridGeometry, envelope, bands, null, null);
}
/**
* Construct a grid coverage with the specified envelope. A default set of
* {@linkplain SampleDimension sample dimensions} is used.
*
* @param name The grid coverage name.
* @param image The image.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods. The
* number of dimensions must matches the number of dimensions for
* envelope
.
* @param envelope The grid coverage cordinates. This envelope must have at least two
* dimensions. The two first dimensions describe the image location
* along x and y axis. The other dimensions are
* optional and may be used to locate the image on a vertical axis or
* on the time axis.
*
* @throws MismatchedDimensionException If the envelope's dimension
* is not the same than the coordinate system's dimension.
*/
public GridCoverage(final String name, final RenderedImage image,
final CoordinateSystem cs, final Envelope envelope)
throws MismatchedDimensionException
{
this(name, image, cs, envelope, null, null, null);
}
/**
* Construct a grid coverage with the specified envelope and sample dimensions.
*
* @param name The grid coverage name.
* @param image The image.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods. The
* number of dimensions must matches the number of dimensions for
* envelope
.
* @param envelope The grid coverage cordinates. This envelope must have at least two
* dimensions. The two first dimensions describe the image location
* along x and y axis. The other dimensions are
* optional and may be used to locate the image on a vertical axis or
* on the time axis.
* @param bands Sample dimensions for each image band, or null
for
* default sample dimensions. If non-null, then this array's length
* must matches the number of bands in image
.
* @param sources The sources for this grid coverage, or null
if none.
* @param properties The set of properties for this coverage, or null
* if there is none. "Properties" in Java Advanced Imaging is what
* OpenGIS calls "Metadata". There is no getMetadataValue(...)
* method in this implementation. Use {@link #getProperty} instead. Keys may
* be {@link String} or {@link CaselessStringKey} objects, while values may
* be any {@link Object}.
*
* @throws MismatchedDimensionException If the envelope's dimension
* is not the same than the coordinate system's dimension.
* @throws IllegalArgumentException if the number of bands differs
* from the number of sample dimensions.
*/
public GridCoverage(final String name, final RenderedImage image,
final CoordinateSystem cs, final Envelope envelope,
final SampleDimension[] bands, final GridCoverage[] sources,
final Map properties)
throws MismatchedDimensionException
{
this(name, PlanarImage.wrapRenderedImage(image), cs, null,
(Envelope)envelope.clone(), bands, sources, properties);
}
/**
* Construct a grid coverage with the specified transform and sample dimension.
* This is the most general constructor, the one that gives the maximum control
* on the grid coverage to be created.
*
* @param name The grid coverage name.
* @param image The image.
* @param cs The coordinate system. This specifies the coordinate system used
* when accessing a grid coverage with the "evaluate" methods. The
* number of dimensions must matches the number of dimensions for
* gridToCS
.
* @param gridToCS The math transform from grid to coordinate system.
* @param bands Sample dimensions for each image band, or null
for
* default sample dimensions. If non-null, then this array's length
* must matches the number of bands in image
.
* @param sources The sources for this grid coverage, or null
if none.
* @param properties The set of properties for this coverage, or null
* if there is none. "Properties" in Java Advanced Imaging is what
* OpenGIS calls "Metadata". There is no getMetadataValue(...)
* method in this implementation. Use {@link #getProperty} instead. Keys may
* be {@link String} or {@link CaselessStringKey} objects, while values may
* be any {@link Object}.
*
* @throws MismatchedDimensionException If the transform's dimension
* is not the same than the coordinate system's dimension.
* @throws IllegalArgumentException if the number of bands differs
* from the number of sample dimensions.
*/
public GridCoverage(final String name, final RenderedImage image,
final CoordinateSystem cs, final MathTransform gridToCS,
final SampleDimension[] bands, final GridCoverage[] sources,
final Map properties)
throws MismatchedDimensionException
{
this(name, PlanarImage.wrapRenderedImage(image), cs,
new GridGeometry(null, gridToCS),
null, bands, sources, properties);
}
/**
* Construct a grid coverage. This private constructor expect an envelope
* (envelope
) or a grid geometry (gridGeometry
).
* One and only one of those argument should be non-null.
* The null arguments will be computed from the non-null argument.
*/
private GridCoverage(final String name,
final PlanarImage image,
final CoordinateSystem cs,
GridGeometry gridGeometry, // ONE and only one of those
Envelope envelope, // two arguments should be non-null.
final SampleDimension[] sdBands,
final GridCoverage[] sources,
final Map properties)
throws MismatchedDimensionException
{
super(name, cs, image, properties);
if ((gridGeometry==null) == (envelope==null)) {
// Should not happen
throw new AssertionError();
}
if (sources != null) {
this.sources = (GridCoverage[]) sources.clone();
} else {
this.sources = null;
}
this.image = image;
/*
* Check sample dimensions. The number of SampleDimensions must matches the
* number of image's bands (this is checked by GridSampleDimension.create).
*/
sampleDimensions = new SampleDimension[image.getNumBands()];
isGeophysics = GridSampleDimension.create(name, image, sdBands, sampleDimensions);
/*
* Constructs the grid range and the envelope if they were not explicitly provided.
* The envelope computation (if needed) requires a valid 'gridToCoordinateSystem'
* transform in the GridGeometry. Otherwise, no transform are required. The range
* will be inferred from the image size, if needed. In any cases, the envelope must
* be non-empty and its dimension must matches the coordinate system's dimension. A
* pool of shared envelopes will be used in order to recycle existing envelopes.
*/
final GridRange gridRange;
if (LegacyGCSUtilities.hasGridRange(gridGeometry)) {
gridRange = gridGeometry.getGridRange();
} else {
gridRange = new GridRange(image, cs.getDimension());
if (LegacyGCSUtilities.hasTransform(gridGeometry)) {
gridGeometry=new GridGeometry(gridRange, gridGeometry.getGridToCoordinateSystem());
}
}
// Check the GridRange...
if (true) {
final String error = checkConsistency(image, gridRange);
if (error != null) {
throw new IllegalArgumentException(error);
}
}
// Check the Envelope...
if (envelope == null) {
envelope = gridGeometry.getEnvelope();
}
final int dimension = envelope.getDimension();
if (envelope.isEmpty() || dimension<2) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.EMPTY_ENVELOPE));
}
if (dimension != cs.getDimension()) {
throw new MismatchedDimensionException(Errors.format(
ErrorKeys.MISMATCHED_DIMENSION_$2,
new Integer(cs.getDimension()), new Integer(envelope.getDimension())));
}
this.envelope = (Envelope)pool.canonicalize(envelope);
/*
* Compute the grid geometry. The math transform will be computed from the envelope.
* A pool of shared grid geometries will be used in order to recycle existing objects.
*
* Note: Should we invert some axis? For example, the 'y' axis is often inversed
* (since image use a downward 'y' axis). If all source grid coverages use
* the same axis orientations, we will reuse those orientations. Otherwise,
* we will use default orientations where only the 'y' axis is inversed.
*/
if (gridGeometry == null) {
boolean[] inverse = null;
if (sources != null) {
for (int i=0; iGridCoverage
are immutable by design,
* we are not allowed to propagate the image change here. The {@link #getGridGeometry} method
* will thrown an {@link IllegalStateException} in this case.
*/
private static String checkConsistency(final RenderedImage image, final GridRange range) {
for (int i=0; i<=1; i++) {
final int min, length;
final Object label;
switch (i) {
case 0: min=image.getMinX(); length=image.getWidth(); label="\"X\""; break;
case 1: min=image.getMinY(); length=image.getHeight(); label="\"Y\""; break;
default: min=0; length=1; label=new Integer(i); break;
}
if (range.getLower(i)!=min || range.getLength(i)!=length) {
return Errors.format(ErrorKeys.BAD_GRID_RANGE_$3, label,
new Integer(min), new Integer(min+length));
}
}
return null;
}
/**
* Returns true
if grid data can be edited. The default
* implementation returns true
if {@link #image} is an
* instance of {@link WritableRenderedImage}.
*
* @see GC_GridCoverage#isDataEditable
*/
public boolean isDataEditable() {
return (image instanceof WritableRenderedImage);
}
/**
* Returns the source data for a grid coverage. If the GridCoverage
* was produced from an underlying dataset, the returned list is an empty list.
* If the GridCoverage
was produced using
* {@link org.geotools.gp.GridCoverageProcessor} then it should return the source
* grid coverage of the one used as input to GridCoverageProcessor
.
* In general the getSources()
method is intended to return the original
* GridCoverage
on which it depends. This is intended to allow applications
* to establish what GridCoverage
s will be affected when others are updated,
* as well as to trace back to the "raw data".
*/
public GridCoverage[] getSources() {
return (sources!=null) ? (GridCoverage[]) sources.clone() : EMPTY_LIST;
}
/**
* Returns information for the grid coverage geometry. Grid geometry
* includes the valid range of grid coordinates and the georeferencing.
*
* @see GC_GridCoverage#getGridGeometry
*/
public GridGeometry getGridGeometry() {
final String error = checkConsistency(image, gridGeometry.getGridRange());
if (error != null) {
throw new IllegalStateException(error);
}
return gridGeometry;
}
/**
* Returns The bounding box for the coverage domain in coordinate
* system coordinates.
*/
public Envelope getEnvelope() {
return (Envelope) envelope.clone();
}
/**
* Retrieve sample dimension information for the coverage.
* For a grid coverage, a sample dimension is a band. The sample dimension information
* include such things as description, data type of the value (bit, byte, integer...),
* the no data values, minimum and maximum values and a color table if one is associated
* with the dimension. A coverage must have at least one sample dimension.
*/
public SampleDimension[] getSampleDimensions() {
return (SampleDimension[]) sampleDimensions.clone();
}
/**
* Returns the interpolation used for all evaluate(...)
methods.
* The default implementation returns {@link InterpolationNearest}.
*
* @return The interpolation.
*/
public Interpolation getInterpolation() {
return Interpolation.getInstance(Interpolation.INTERP_NEAREST);
}
/**
* Returns a sequence of integer values for a given 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 CoordinatePoint coord, final int[] dest)
throws CannotEvaluateException
{
return evaluate(new Point2D.Double(coord.ord[0], coord.ord[1]), dest);
}
/**
* Returns a sequence of float values for a given 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 CoordinatePoint coord, final float[] dest)
throws CannotEvaluateException
{
return evaluate(new Point2D.Double(coord.ord[0], coord.ord[1]), dest);
}
/**
* Returns a sequence of double values for a given 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 CoordinatePoint coord, final double[] dest)
throws CannotEvaluateException
{
return evaluate(new Point2D.Double(coord.ord[0], coord.ord[1]), dest);
}
/**
* Returns a 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, final int[] dest)
throws CannotEvaluateException
{
final Point2D pixel = gridGeometry.inverseTransform(coord);
final double fx = pixel.getX();
final double fy = pixel.getY();
if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
final int x = (int)Math.round(fx);
final int y = (int)Math.round(fy);
if (image.getBounds().contains(x,y)) { // getBounds() returns a cached instance.
return image.getTile(image.XToTileX(x), image.YToTileY(y)).getPixel(x, y, dest);
}
}
throw new PointOutsideCoverageException(coord);
}
/**
* Returns a 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, final float[] dest)
throws CannotEvaluateException
{
final Point2D pixel = gridGeometry.inverseTransform(coord);
final double fx = pixel.getX();
final double fy = pixel.getY();
if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
final int x = (int)Math.round(fx);
final int y = (int)Math.round(fy);
if (image.getBounds().contains(x,y)) { // getBounds() returns a cached instance.
return image.getTile(image.XToTileX(x), image.YToTileY(y)).getPixel(x, y, dest);
}
}
throw new PointOutsideCoverageException(coord);
}
/**
* Returns a 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, final double[] dest)
throws CannotEvaluateException
{
final Point2D pixel = gridGeometry.inverseTransform(coord);
final double fx = pixel.getX();
final double fy = pixel.getY();
if (!Double.isNaN(fx) && !Double.isNaN(fy)) {
final int x = (int)Math.round(fx);
final int y = (int)Math.round(fy);
if (image.getBounds().contains(x,y)) { // getBounds() returns a cached instance.
return image.getTile(image.XToTileX(x), image.YToTileY(y)).getPixel(x, y, dest);
}
}
throw new PointOutsideCoverageException(coord);
}
/**
* Returns a debug string for the specified coordinate. This method produces a
* string with pixel coordinates and pixel values for all bands (with geophysics
* values or category name in parenthesis). Example for a 1-banded image:
*
* * * @param coord The coordinate point where to evaluate. * @return A string with pixel coordinates and pixel values at the specified location, * or(1171,1566)=[196 (29.6 °C)]
null
if coord
is outside coverage.
*/
public synchronized String getDebugString(final CoordinatePoint coord) {
Point2D pixel = new Point2D.Double(coord.ord[0], coord.ord[1]);
pixel = gridGeometry.inverseTransform(pixel);
final int x = (int)Math.round(pixel.getX());
final int y = (int)Math.round(pixel.getY());
if (image.getBounds().contains(x,y)) { // getBounds() returns a cached instance.
final int numBands = image.getNumBands();
final Raster raster = image.getTile(image.XToTileX(x), image.YToTileY(y));
final int datatype = image.getSampleModel().getDataType();
final StringBuffer buffer = new StringBuffer();
buffer.append('(');
buffer.append(x);
buffer.append(',');
buffer.append(y);
buffer.append(")=[");
for (int band=0; bandN
dimensional array will need to be offset by grid range
* minimum coordinates to get equivalent grid coordinates.
*/
// public abstract DoubleMultiArray getDataBlockAsDouble(final GridRange range)
// {
// TODO: Waiting for multiarray package (JSR-083)!
// Same for setDataBlock*
// }
/**
* Returns grid data as a rendered image.
*/
public RenderedImage getRenderedImage() {
return image;
}
/**
* Hints that the given area may be needed in the near future. Some implementations
* may spawn a thread or threads to compute the tiles while others may ignore the hint.
*
* @param area A rectangle indicating which geographic area to prefetch.
* This area's coordinates must be expressed according the
* grid coverage's coordinate system, as given by
* {@link #getCoordinateSystem}.
*/
public void prefetch(final Rectangle2D area) {
final Point[] tileIndices=image.getTileIndices(gridGeometry.inverseTransform(area));
if (tileIndices!=null) {
image.prefetchTiles(tileIndices);
}
}
/**
* If true
, returns the geophysics companion of this grid coverage. In a
* geophysics grid coverage, all sample values are equals to geophysics
* ("real world") values without the need for any transformation. In such geophysics
* coverage, the {@linkplain SampleDimension#getSampleToGeophysics sample to geophysics}
* transform is the identity transform for all sample dimensions. "No data" values are
* expressed by {@linkplain Float#NaN NaN} numbers.
* GridCoverage
objects live by pair: a geophysics one (used for
* computation) and a non-geophysics one (used for packing data, usually as
* integers). The geo
argument specifies which object from the pair is wanted,
* regardless if this method is invoked on the geophysics or non-geophysics instance of the
* pair. In other words, the result of geophysics(b1).geophysics(b2).geophysics(b3)
* depends only on the value in the last call (b3
).
*
* @param geo true
to get a grid coverage with sample values equals to geophysics
* values, or false
to get the packed version.
* @return The grid coverage. Never null
, but may be this
.
*
* @see SampleDimension#geophysics
* @see Category#geophysics
* @see LookupDescriptor
* @see RescaleDescriptor
* @see PiecewiseDescriptor
*/
public GridCoverage geophysics(final boolean geo) {
if (geo == isGeophysics) {
return this;
}
if (inverse != null) {
return inverse;
}
if (!LegacyGCSUtilities.hasTransform(sampleDimensions)) {
return inverse=this;
}
synchronized (this) {
inverse = createGeophysics(geo);
inverse = interpolate(inverse);
if (inverse.inverse == null) {
inverse.inverse = this;
} else if (inverse.inverse != this) {
final Locale locale = null;
throw new RasterFormatException(Vocabulary.getResources(locale).getString(
ErrorKeys.COVERAGE_ALREADY_BOUND_$2,
"createGeophysics", inverse.inverse.getName(locale)));
}
return inverse;
}
}
/**
* Invoked by {@link #geophysics(boolean)} when the packed or geophysics companion of this
* grid coverage need to be created. Subclasses may override this method in order to modify
* the object to be created.
*
* @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.
*
* @task HACK: IndexColorModel seems to badly choose its sample model. As of JDK 1.4-rc1, it
* construct a ComponentSampleModel, which is drawn very slowly to the screen. A
* much faster sample model is PixelInterleavedSampleModel, which is the sample
* model used by BufferedImage for TYPE_BYTE_INDEXED. We should check if this is
* fixed in future J2SE release.
*
* @task HACK: This method provides an optimisation for the case of a linear transformations:
* it use the JAI's "Rescale" or "Piecewise" operations, which may be hardware
* accelerated. Unfortunatly, bug #4726416 prevent us to use this optimisation
* with JAI 1.1.1. The optimisation is enabled only if we are running JAI 1.1.2.
* This hack should be removed when JAI 1.1.2 will be widely available.
*
* @task HACK: The "Piecewise" operation is disabled because javac 1.4.1_01 generate illegal
* bytecode. This bug is fixed in javac 1.4.2-beta. However, we still have an
* ArrayIndexOutOfBoundsException in JAI code...
*
* @task REVISIT: A special case (exactly one linear relationship with one NaN value mapping
* exactly to the index value 0) was optimized to the "Rescale" operation in
* previous version. This case is very common, which make this optimization a
* usefull one. Unfortunatly, it had to be disabled because there is nothing
* in the "Rescale" preventing some real number (not NaN) to maps to 0 through
* the normal linear relationship. Note that the optimization worked well in
* previous version except for the above-cited problem. We can very easily re-
* enable it later if we know the range of values really stored in the image
* (as of JAI's "extrema" operation). If would suffice to add a check making
* sure that the range of transformed values doesn't contains 0.
*/
protected GridCoverage createGeophysics(final boolean geo) {
/*
* STEP 1 - Gets the source image and prepare the target sample dimensions.
* As a slight optimisation, we skip the "Null" operations since
* such image may be the result of some "Colormap" operation.
*/
PlanarImage image = this.image;
while (image instanceof NullOpImage) {
final NullOpImage op = (NullOpImage) image;
if (op.getNumSources() != 1) {
break;
}
image = op.getSourceImage(0);
}
final int numBands = image.getNumBands();
final int visibleBand = LegacyGCSUtilities.getVisibleBand(image);
final SampleDimension[] targetBands = (SampleDimension[]) sampleDimensions.clone();
assert targetBands.length == numBands : targetBands.length;
for (int i=0; icoverage
but
* the same interpolation than this
.
*
* @deprecated Override {@link #createGeophysics} instead.
* This method will be removed in a future version.
*/
protected GridCoverage interpolate(final GridCoverage coverage) {
// This method is overriden by org.geotools.gp.Interpolator
return coverage;
}
/**
* Construct the {@link PlanarImage} from the {@linkplain SerializableRenderedImage}
* after deserialization.
*/
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
image = PlanarImage.wrapRenderedImage(serializedImage);
}
/**
* Serialize this grid coverage. Before serialization, a {@linkplain SerializableRenderedImage
* serializable rendered image} is created if it was not already done.
*
* @todo Check image's sources in case the planar image is just a wrapper around
* an existing {@linkplain SerializableRenderedImage}.
*/
private void writeObject(final ObjectOutputStream out) throws IOException {
if (serializedImage == null) {
serializedImage = new SerializableRenderedImage(image, false, null, "gzip", null, null);
}
out.defaultWriteObject();
}
/**
* Returns a string représentation of this coverage. This string is
* for debugging purpose only and may change in future version.
*/
public String toString() {
final String lineSeparator = System.getProperty("line.separator", "\n");
final StringWriter buffer = new StringWriter();
buffer.write(super.toString());
final LineWriter filter = new LineWriter(buffer, lineSeparator+" ");
try {
filter.write(lineSeparator);
for (int i=0; ievaluate(...)
methods.
*
* @return The interpolation.
* @throws RemoteException if the remote connection failed.
*/
public abstract Interpolation getInterpolation() throws RemoteException;
}
/**
* Export a Geotools {@link GridCoverage} as an OpenGIS {@link GC_GridCoverage}
* object. This class is suitable for RMI use. User should not create instance
* of this class directly. The method {@link Adapters#export(GridCoverage)} should
* be used instead.
*
* @version $Id$
* @author Martin Desruisseaux
*/
protected class Export extends Coverage.Export implements GC_GridCoverage, Remote {
/**
* The serialized {@link RenderedImage}, or null
if this image
* is not yet serialized.
*/
private SerializableRenderedImage serialized;
/**
* Constructs a remote object. This object is automatically added to the cache
* in the enclosing {@link GridCoverage} object. The cached Export
* instance can be queried with {@link Adapters#export(GridCoverage)}.
*
* @param adapters The originating adapter.
* @throws RemoteException if this object can't be exported through RMI.
*/
protected Export(final Adapters adapters) throws RemoteException {
super(adapters);
}
/**
* Returns the number of grid coverages which the grid coverage was derived from.
* The default implementation invokes {@link GridCoverage#getSources}.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public int getNumSources() throws RemoteException {
return GridCoverage.this.getSources().length;
}
/**
* Returns the source data for a grid coverage.
* The default implementation invokes {@link GridCoverage#getSources}.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public GC_GridCoverage getSource(int sourceDataIndex) throws RemoteException {
return ((Adapters) adapters).export(GridCoverage.this.getSources()[sourceDataIndex]);
}
/**
* Returns true
if grid data can be edited.
* The default implementation invokes {@link GridCoverage#isDataEditable}.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public boolean isDataEditable() throws RemoteException {
return GridCoverage.this.isDataEditable();
}
/**
* Returns information for the packing of grid coverage values.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public GC_GridPacking getGridPacking() throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns information for the grid coverage geometry.
* The default implementation invokes {@link GridCoverage#getGridGeometry}.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public GC_GridGeometry getGridGeometry() throws RemoteException {
return ((Adapters) adapters).export(GridCoverage.this.getGridGeometry());
}
/**
* Return the grid geometry for an overview.
* The default implementation throws an {@link ArrayIndexOutOfBoundsException},
* since {@link #getNumOverviews} returned 0.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public GC_GridGeometry getOverviewGridGeometry(int overviewIndex) throws RemoteException {
throw new ArrayIndexOutOfBoundsException(overviewIndex);
}
/**
* Returns the number of predetermined overviews for the grid.
* The default implementation returns 0, since this feature is not yet
* implemented. It may be implemented in a future version.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public int getNumOverviews() throws RemoteException {
return 0;
}
/**
* Returns a pre-calculated overview for a grid coverage.
* The default implementation throws an {@link ArrayIndexOutOfBoundsException},
* since {@link #getNumOverviews} returned 0.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public GC_GridCoverage getOverview(int overviewIndex) throws RemoteException {
throw new ArrayIndexOutOfBoundsException(overviewIndex);
}
/**
* Returns the optimal size to use for each dimension when accessing grid values.
* The default implementation returns the image's tiles size.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public int[] getOptimalDataBlockSizes() throws RemoteException {
final int[] size = new int[getDimension()];
switch (size.length) {
default: Arrays.fill(size, 1); // Fall through
case 2: size[1] = image.getTileHeight(); // Fall through
case 1: size[0] = image.getTileWidth(); // Fall through
case 0: break;
}
return size;
}
/**
* Return a sequence of boolean values for a block.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public boolean[] getDataBlockAsBoolean(GC_GridRange gridRange) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Return a sequence of byte values for a block.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public byte[] getDataBlockAsByte(GC_GridRange gridRange) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Return a sequence of int values for a block.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public int[] getDataBlockAsInteger(GC_GridRange gridRange) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Return a sequence of double values for a block.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public double[] getValueBlockAsDouble(GC_GridRange gridRange) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Return a block of grid coverage data for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public byte[] getPackedDataBlock(GC_GridRange gridRange) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Set a block of boolean values for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public void setDataBlockAsBoolean(GC_GridRange gridRange, boolean[] values) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Set a block of byte values for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public void setDataBlockAsByte(GC_GridRange gridRange, byte[] values) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Set a block of bint values for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public void setDataBlockAsInteger(GC_GridRange gridRange, int[] values) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Set a block of double values for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public void setDataBlockAsDouble(GC_GridRange gridRange, double[] values) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Set a block of grid coverage data for all sample dimensions.
*
* @throws RemoteException if a remote call failed. More specifically, the exception will
* be an instance of {@link ServerException} if an error occurs on the server side.
*/
public void setPackedDataBlock(GC_GridRange gridRange, byte [] values) throws RemoteException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Returns the underlying {@link RenderedImage} for this {@link GC_GridCoverage}.
* This method usually returns an instance of {@link SerializableRenderedImage},
* which is appropriate for use through a RMI API.
*
* @throws RemoteException if the remote connection failed.
* @throws NotSerializableException if the image is not serializable.
*/
public synchronized RenderedImage getRenderedImage() throws IOException {
if (serialized == null) {
serialized = new SerializableRenderedImage(GridCoverage.this.getRenderedImage(),
false, null, "gzip", null, null);
}
return serialized;
}
/**
* Returns the interpolation used for evaluate(...)
methods.
*
* @return The interpolation.
* @throws RemoteException if the remote connection failed.
*/
public Interpolation getInterpolation() throws RemoteException {
return GridCoverage.this.getInterpolation();
}
}
/**
* Mimic a GeoAPI interface as a legacy implementation. This method is provided
* as a temporary bridge for using new CRS object with J2D-Renderer for example.
*/
public static GridCoverage fromGeoAPI(org.opengis.coverage.grid.GridCoverage gc) {
if (gc instanceof GridCoverage) {
return (GridCoverage) gc;
}
final org.opengis.referencing.crs.CoordinateReferenceSystem crs;
final org.opengis.referencing.operation.MathTransform gridToCRS;
final boolean isGeo;
if (gc instanceof GridCoverage2D) {
final GridCoverage2D gc2D = (GridCoverage2D) gc;
gc = gc2D.geophysics(false);
isGeo = (gc != gc2D);
crs = gc2D.getCoordinateReferenceSystem2D();
gridToCRS = ((org.geotools.coverage.grid.GridGeometry2D) gc2D.getGridGeometry())
.getGridToCoordinateSystem2D();
} else {
isGeo = false;
crs = gc.getCoordinateReferenceSystem();
gridToCRS = gc.getGridGeometry().getGridToCoordinateSystem();
}
final SampleDimension[] sd = new SampleDimension[gc.getNumSampleDimensions()];
for (int i=0; i