/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.coverage.grid;
import java.util.List;
import java.util.Random;
import java.util.AbstractList;
import java.awt.Color;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import javax.imageio.ImageIO;
import javax.media.jai.RasterFactory;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.geotools.factory.Hints;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.CoverageTestBase;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.transform.LinearTransform1D;
import org.geotools.referencing.operation.transform.ConcatenatedTransform;
import org.geotools.referencing.operation.transform.ExponentialTransform1D;
import org.geotools.test.TestData;
import static java.awt.Color.decode;
import static javax.measure.unit.SI.*;
import static org.geotools.util.NumberRange.create;
import static org.junit.Assert.*;
/**
* Base class for grid coverage tests. This base class provides factory methods for sample
* {@link GridCoverage2D}, and {@code assertEqual} methods for comparing values.
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class GridCoverageTestBase extends CoverageTestBase {
/**
* Random number generator for this test.
*/
private static final Random random = new Random(684673898634768L);
/**
* Returns a grid coverage filled with random values. The coordinate
* reference system default to {@link DefaultGeographicCRS#WGS84}.
*
* @return A random coverage.
*/
protected static GridCoverage2D getRandomCoverage() {
return getRandomCoverage(DefaultGeographicCRS.WGS84);
}
/**
* Returns a grid coverage filled with random values.
*
* @param crs The coverage coordinate reference system.
* @return A random coverage.
*/
protected static GridCoverage2D getRandomCoverage(final CoordinateReferenceSystem crs) {
/*
* Some constants used for the construction and tests of the grid coverage.
*/
final double SCALE = 0.1; // Scale factor for pixel transcoding.
final double OFFSET = 5.0; // Offset factor for pixel transcoding.
final double PIXEL_SIZE = .25; // Pixel size (in degrees). Used in transformations.
final int BEGIN_VALID = 3; // The minimal valid index for quantitative category.
/*
* Constructs the grid coverage. We will assume that the grid coverage use
* (longitude,latitude) coordinates, pixels of 0.25 degrees and a lower
* left corner at 10°W 30°N.
*/
final GridCoverage2D coverage; // The final grid coverage.
final BufferedImage image; // The GridCoverage's data.
final WritableRaster raster; // The image's data as a raster.
final Rectangle2D bounds; // The GridCoverage's envelope.
final GridSampleDimension band; // The only image's band.
band = new GridSampleDimension("Temperature", new Category[] {
new Category("No data", null, 0),
new Category("Land", null, 1),
new Category("Cloud", null, 2),
new Category("Temperature", null, BEGIN_VALID, 256, SCALE, OFFSET)
}, CELSIUS);
image = new BufferedImage(120, 80, BufferedImage.TYPE_BYTE_INDEXED);
raster = image.getRaster();
for (int i=raster.getWidth(); --i>=0;) {
for (int j=raster.getHeight(); --j>=0;) {
raster.setSample(i,j,0, random.nextInt(256));
}
}
bounds = new Rectangle2D.Double(-10, 30, PIXEL_SIZE*image.getWidth(),
PIXEL_SIZE*image.getHeight());
final GeneralEnvelope envelope = new GeneralEnvelope(crs);
envelope.setRange(0, bounds.getMinX(), bounds.getMaxX());
envelope.setRange(1, bounds.getMinY(), bounds.getMaxY());
for (int i=envelope.getDimension(); --i>=2;) {
final double min = 10 * i;
envelope.setRange(i, min, min + 5);
}
final Hints hints = new Hints(Hints.TILE_ENCODING, "raw");
final GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(hints);
coverage = factory.create("Test", image, envelope, new GridSampleDimension[] {band}, null, null);
assertEquals("raw", coverage.tileEncoding);
/*
* Grid coverage construction finished. Now test it. First we test the creation of a
* "geophysics" view. This test make sure that the 'view(type)' method does not create
* more grid coverages than needed.
*/
assertSame(coverage.getRenderedImage(), coverage.getRenderableImage(0,1).createDefaultRendering());
assertSame(image.getTile(0,0), coverage.getRenderedImage().getTile(0,0));
GridCoverage2D geophysics = coverage.view(ViewType.GEOPHYSICS);
assertSame(coverage, coverage.view(ViewType.PACKED));
assertSame(coverage, geophysics.view(ViewType.PACKED));
assertSame(geophysics, geophysics.view(ViewType.GEOPHYSICS));
assertFalse( coverage.equals(geophysics));
assertFalse( coverage.getSampleDimension(0).getSampleToGeophysics().isIdentity());
assertTrue(geophysics.getSampleDimension(0).getSampleToGeophysics().isIdentity());
/*
* Compares data.
*/
final int bandN = 0; // Band to test.
double[] bufferCov = null;
double[] bufferGeo = null;
final double left = bounds.getMinX() + (0.5*PIXEL_SIZE); // Includes translation to center
final double upper = bounds.getMaxY() - (0.5*PIXEL_SIZE); // Includes translation to center
final Point2D.Double point = new Point2D.Double(); // Will maps to pixel center.
for (int j=raster.getHeight(); --j>=0;) {
for (int i=raster.getWidth(); --i>=0;) {
point.x = left + PIXEL_SIZE*i;
point.y = upper - PIXEL_SIZE*j;
double r = raster.getSampleDouble(i,j,bandN);
bufferCov = coverage.evaluate(point, bufferCov);
bufferGeo = geophysics.evaluate(point, bufferGeo);
assertEquals(r, bufferCov[bandN], EPS);
// Compares transcoded samples.
if (r < BEGIN_VALID) {
assertTrue(Double.isNaN(bufferGeo[bandN]));
} else {
assertEquals(OFFSET + SCALE*r, bufferGeo[bandN], EPS);
}
}
}
return coverage;
}
/**
* An immutable list of grid coverages to be used for testing purpose. Coverages are read
* when a the {@code get(int)} method is invoked.
*/
protected static final List<GridCoverage2D> EXAMPLES = new AbstractList<GridCoverage2D>() {
/**
* The coverages returned by previous invocations.
*/
private final GridCoverage2D[] cached = new GridCoverage2D[5];
/**
* Returns the number of available coverages which may be used as example.
*/
public int size() {
return cached.length;
}
/**
* Returns a {@link GridCoverage} which may be used as a "real world" example.
*
* @param number The example number, from 0 inclusive to {@link #size()} exclusive.
* @return The "real world" grid coverage.
*/
public synchronized GridCoverage2D get(final int number) {
GridCoverage2D coverage = cached[number];
if (coverage == null) {
cached[number] = coverage = load(number);
}
return coverage;
}
/**
* Loads the image at the given index. This is invoked by {@link #get}
* the first time a given coverage is requested.
*/
private GridCoverage2D load(final int number) {
final GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
final String path;
final Category[] categories;
final CoordinateReferenceSystem crs;
final Rectangle2D bounds;
final GridSampleDimension[] bands;
switch (number) {
default: {
throw new IndexOutOfBoundsException(String.valueOf(number));
}
/* ------------------------------------------------------------
* Thematic : Sea Surface Temperature (SST) in °C
* Data packaging : Indexed 8-bits
* Nodata values : [0 .. 29] and [240 .. 255] inclusive.
* Conversion formula : (°C) = (packed value)/10 + 10
* Geographic extent : (41°S, 35°E) - (5°N, 80°E)
* Image size : (450 x 460) pixels
*
* This is a raster from Earth observations using a relatively straightforward
* conversion formula to geophysics values (a linear transform using the usual
* scale and offset parameters, in this case 0.1 and 10 respectively). The
* interresting part of this example is that it contains a lot of nodata values.
*/
case 0: {
path = "QL95209.png";
crs = DefaultGeographicCRS.WGS84;
categories = new Category[] {
new Category("Coast line", decode("#000000"), create( 0, 0)),
new Category("Cloud", decode("#C3C3C3"), create( 1, 9)),
new Category("Unused", decode("#822382"), create( 10, 29)),
new Category("Sea Surface Temperature", null, create( 30, 219), 0.1, 10.0),
new Category("Unused", decode("#A0505C"), create(220, 239)),
new Category("Land", decode("#D2C8A0"), create(240, 254)),
new Category("No data", decode("#FFFFFF"), create(255, 255)),
};
bounds = new Rectangle2D.Double(35, -41, 45, 46);
bands = new GridSampleDimension[] {
new GridSampleDimension("Measure", categories, CELSIUS)
};
break;
}
/* ------------------------------------------------------------
* Thematic : Chlorophyle-a concentration in mg/m³
* Data packaging : Indexed 8-bits
* Nodata values : 0 and 255
* Conversion formula : (mg/m³) = 10 ^ ((packed value)*0.015 - 1.985)
* Geographic extent : (34°N, 07°W) - (45°N, 12°E)
* Image size : (300 x 175) pixels
*
* This is a raster from Earth observations using a more complex conversion
* formula to geophysics values (an exponential one). The usual scale and
* offset parameters are not enough in this case.
*/
case 1: {
path = "CHL01195.png";
crs = DefaultGeographicCRS.WGS84;
final MathTransform1D sampleToGeophysics = (MathTransform1D)
ConcatenatedTransform.create(LinearTransform1D.create(0.015, -1.985),
ExponentialTransform1D.create(10, 1));
categories = new Category[] {
new Category("Land", decode("#000000"), create(255, 255)),
new Category("No data", decode("#FFFFFF"), create( 0, 0)),
new Category("Chl-a", null, create( 1, 254), sampleToGeophysics)
};
bounds = new Rectangle2D.Double(-7, 34, 19, 11);
bands = new GridSampleDimension[] {
new GridSampleDimension("Measure", categories, MILLI(GRAM).divide(CUBIC_METRE))
};
break;
}
/* ------------------------------------------------------------
* Thematic : World Digital Elevation Model (DEM)
* Geographic extent : (90°S, 180°W) - (90°N, 180°E)
*/
case 2: {
path = "world_dem.gif";
bounds = new Rectangle2D.Double(-180, -90, 360, 180);
crs = DefaultGeographicCRS.WGS84;
bands = null;
break;
}
/* ------------------------------------------------------------
* Thematic : World Bathymetry (DEM)
* Geographic extent : (90°S, 180°W) - (90°N, 180°E)
*/
case 3:{
path = "BATHY.png";
bounds = new Rectangle2D.Double(-180, -90, 360, 180);
crs = DefaultGeographicCRS.WGS84;
bands = null;
break;
}
/*
* A float coverage. Because we use only one tile with one band, the code below
* is pretty similar to the code we would have if we were just setting the values
* in a matrix.
*/
case 4: {
final int width = 500;
final int height = 500;
WritableRaster raster =
RasterFactory.createBandedRaster(DataBuffer.TYPE_FLOAT, width, height, 1, null);
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
raster.setSample(x, y, 0, x+y);
}
}
final Color[] colors = new Color[] {
Color.BLUE, Color.CYAN, Color.WHITE, Color.YELLOW, Color.RED
};
return factory.create("Float coverage", raster,
new Envelope2D(DefaultGeographicCRS.WGS84, 35, -41, 35+45, -41+46),
null, null, null, new Color[][] {colors}, null);
}
}
/*
* Now creates the coverage from the informations selected in the above switch
* statement.
*/
final RenderedImage image;
try {
image = ImageIO.read(TestData.getResource(GridCoverageTestBase.class, path));
} catch (IOException e) {
throw new AssertionError(e);
}
final String filename = new File(path).getName();
final GeneralEnvelope envelope = new GeneralEnvelope(bounds);
envelope.setCoordinateReferenceSystem(crs);
return factory.create(filename, image, envelope, bands, null, null);
}
};
/**
* Tests the serialization of the packed and geophysics views of a grid coverage.
*
* @param coverage The coverage to serialize.
* @return The deserialized grid coverage as packed view.
* @throws IOException if an I/O operation was needed and failed.
* @throws ClassNotFoundException Should never happen.
*/
protected static GridCoverage2D serialize(GridCoverage2D coverage)
throws IOException, ClassNotFoundException
{
coverage.tileEncoding = null;
/*
* The previous line is not something that we should do.
* But we want to test the default GridCoverage2D encoding.
*/
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
final ObjectOutputStream out = new ObjectOutputStream(buffer);
try {
out.writeObject(coverage.view(ViewType.PACKED));
out.writeObject(coverage.view(ViewType.GEOPHYSICS));
} finally {
out.close();
}
final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
GridCoverage2D read;
try {
read = (GridCoverage2D) in.readObject(); assertSame(read, read.view(ViewType.PACKED));
read = (GridCoverage2D) in.readObject(); assertSame(read, read.view(ViewType.GEOPHYSICS));
} finally {
in.close();
}
coverage = read.view(ViewType.PACKED);
assertNotSame(read, coverage);
return coverage;
}
}