/*
* 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.renderer.lite.gridcoverage2d;
import it.geosolutions.imageio.plugins.arcgrid.AsciiGridsImageReader;
import it.geosolutions.imageio.plugins.arcgrid.spi.AsciiGridsImageReaderSpi;
import java.awt.Color;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.imageio.stream.FileImageInputStream;
import javax.media.jai.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.RasterFactory;
import javax.media.jai.RenderedOp;
import junit.framework.TestCase;
import org.geotools.TestData;
import org.geotools.resources.image.ComponentColorModelJAI;
import org.geotools.util.NumberRange;
import org.junit.Before;
import org.junit.Test;
import org.opengis.referencing.operation.TransformException;
/**
*
* @author Simone Giannecchini, GeoSolutions
*
*
* @source $URL$
*/
public class TestLinearClassifier extends TestCase {
@Before
protected void setUp() throws Exception {
// check that it exisits
File file = TestData.copy(this, "arcgrid/arcgrid.zip");
assertTrue(file.exists());
// unzip it
TestData.unzipFile(this, "arcgrid/arcgrid.zip");
}
private static final int TEST_NUM = 1;
static {
RasterClassifier.register(JAI.getDefaultInstance());
}
//
// public static TestSuite suite() {
// TestSuite suite = new TestSuite();
//// suite.addTest(new TestLinearClassifier("testSWAN"));
//// suite.addTest(new TestLinearClassifier("testSWANGAP"));
//// suite.addTest(new TestLinearClassifier("testSynthetic_Double"));
//// suite.addTest(new TestLinearClassifier("testSynthetic_Float"));
// suite.addTest(new TestLinearClassifier("testSpearfish"));
//// suite.addTest(new TestLinearClassifier("testNoDataOnly"));
//
// return suite;
// }
//
// /**
// * Testing Piecewise operation.
// *
// * @throws IOException
// * @throws TransformException
// */
// public void testPiecewise() throws IOException, TransformException {
// // /////////////////////////////////////////////////////////////////////
// //
// // This test is quite standard since the NoData category specified
// // is for NoData values since the input file is a GRASS ascii file
// // where the missing values are represented by * which are substituted
// // with NaN during reads. The only strange thing that we try here is
// // that we map two different classes to the same color with the same
// // index.
// //
// // /////////////////////////////////////////////////////////////////////
// final RenderedImage image = getSpearfhisDemo();
//
// // for (int i = 0; i < TEST_NUM; i++) {
// final LinearColorMapElement c0 = LinearColorMapElement
// .create("c0", Color.yellow, new NumberRange(
// Double.NEGATIVE_INFINITY, false, 1100, true), 5);
//
// final LinearColorMapElement c1 = LinearColorMapElement
// .create("c2", Color.blue, new NumberRange(1100.0, false,
// 1200.0, true), 1);
//
// final LinearColorMapElement c3 = LinearColorMapElement
// .create("c3", Color.green, new NumberRange(1200.0, false,
// 1400.0, true), 7);
//
// final LinearColorMapElement c4 = LinearColorMapElement
// .create("c4", Color.blue, new NumberRange(1400.0, false, 1600,
// true), 1);
//
// final LinearColorMapElement c5 = LinearColorMapElement
// .create("c4", Color.CYAN, new NumberRange(1600.0, false,
// Double.POSITIVE_INFINITY, true), 11);
//
// final LinearColorMapElement c6 = LinearColorMapElement
// .create("nodata", new Color(0, 0, 0, 0), new NumberRange(
// Double.NaN, Double.NaN), 0);
//
// final LinearColorMap list = new LinearColorMap(
// new LinearColorMapElement[] { c0, c1, c3, c4, c5 },
// new LinearColorMapElement[] { c6 });
//
// // assertFalse(10f < 10f + 3 * Float.MIN_VALUE);
// // final int actualNumber = Float.floatToRawIntBits(10f);
// // final int comparisonNumber = Float.floatToRawIntBits(10f)
// // + Float.floatToRawIntBits(3 * Float.MIN_VALUE);
// // assertTrue(-actualNumber + comparisonNumber > 0);
//
// final ParameterBlockJAI pbj = new ParameterBlockJAI("PieceWise");
// pbj.addSource(image);
// final float breakp[][][] = new float[1][2][];
// breakp[0][0] = new float[] { 0f, 10f, 10f + 3f * Float.MIN_VALUE, 20f,
// 20f + 3f * Float.MIN_VALUE, 30f, 30f + 3f * Float.MIN_VALUE,
// 40f, 256f };
// breakp[0][1] = new float[] { 0f, 0f, 1f, 1f, 2f, 2f, 3f, 3f, 4f };
// pbj.setParameter("breakPoints", breakp);
// final RenderedOp d = JAI.create("PieceWise", pbj);
// d.getTiles();
// d.dispose();
//
// }
/**
* Synthetic with Double Sample Model!
*
* @throws IOException
*/
@Test
public void Synthetic_Double() throws IOException {
// /////////////////////////////////////////////////////////////////////
//
// This test is interesting since it can be used to force the
// creation of a sample model that uses a USHORT datatype since the
// number of requested colors is pretty high. We are also using some
// synthetic data where there is no NoData.
//
// /////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////
//
// Set the pixel values. 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.
//
// /////////////////////////////////////////////////////////////////////
final BufferedImage image = getSynthetic_Double();
for (int i = 0; i < TEST_NUM; i++) {
// /////////////////////////////////////////////////////////////////////
//
// Build the categories
//
// /////////////////////////////////////////////////////////////////////
final LinearColorMapElement c0 = LinearColorMapElement
.create("c0", Color.BLACK, NumberRange.create(
Double.NEGATIVE_INFINITY, false, 10, true), 0);
final LinearColorMapElement c1 = LinearColorMapElement
.create("c2", Color.blue, NumberRange.create(10.0, false,
100.0, true), 1);
final LinearColorMapElement c3 = LinearColorMapElement
.create("c3", Color.green, NumberRange.create(100.0, false,
300.0, true), 2);
final LinearColorMapElement c4 = LinearColorMapElement
.create("c4", new Color[] { Color.green, Color.red },
NumberRange.create(300.0, false, 400, true),
NumberRange.create(3, 1000));
final LinearColorMapElement c5 = LinearColorMapElement
.create("c5", new Color[] { Color.red, Color.white },
NumberRange.create(400.0, false, 1000, true),
NumberRange.create(1001, 2000));
final LinearColorMapElement c6 = LinearColorMapElement
.create("c6", Color.red, 1001.0, 2001);
final LinearColorMapElement c7 = LinearColorMapElement
.create("nodata", new Color(0, 0, 0, 0), NumberRange.create(
Double.NaN, Double.NaN), 2201);
final LinearColorMap list = new LinearColorMap("",
new LinearColorMapElement[] { c0, c1, c3, c4, c5, c6 },
new LinearColorMapElement[] { c7 });
final ParameterBlockJAI pbj = new ParameterBlockJAI(
RasterClassifier.OPERATION_NAME);
pbj.addSource(image);
pbj.setParameter("Domain1D", list);
final RenderedOp finalimage = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
if (TestData.isInteractiveTest())
RasterSymbolizerTest.visualize(finalimage, "synthetic");
else
finalimage.getTiles();
finalimage.dispose();
}
}
/**
* Synthetic with Float Sample Model!
*
* @return {@linkplain BufferedImage}
*/
private BufferedImage getSynthetic_Double() {
final int width = 500;
final int height = 500;
final WritableRaster raster = RasterFactory.createBandedRaster(
DataBuffer.TYPE_DOUBLE, 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 ColorModel cm = new ComponentColorModelJAI(ColorSpace
.getInstance(ColorSpace.CS_GRAY), false, false,
Transparency.OPAQUE, DataBuffer.TYPE_DOUBLE);
final BufferedImage image = new BufferedImage(cm, raster, false, null);
return image;
}
/**
* Building a synthetic image upon a DOUBLE sample-model.
*
* @return {@linkplain BufferedImage}
* @throws IOException
*/
@Test
public void Synthetic_Float() throws IOException {
// /////////////////////////////////////////////////////////////////////
//
// This test is interesting since it can be used to force the
// creation of a sample model that uses a USHORT datatype since the
// number of requested colors is pretty high. We are also using some
// synthetic data where there is no NoData.
//
// /////////////////////////////////////////////////////////////////////
// /////////////////////////////////////////////////////////////////////
//
// Set the pixel values. 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.
//
// /////////////////////////////////////////////////////////////////////
final BufferedImage image = getSynthetic_Float();
for (int i = 0; i < TEST_NUM; i++) {
// /////////////////////////////////////////////////////////////////////
//
// Build the categories
//
// /////////////////////////////////////////////////////////////////////
final LinearColorMapElement c0 = LinearColorMapElement
.create("c0", Color.BLACK, NumberRange.create(
Double.NEGATIVE_INFINITY, false, 10, true), 0);
final LinearColorMapElement c1 = LinearColorMapElement
.create("c2", Color.blue, NumberRange.create(10.0f, false,
100.0f, true), 1);
final LinearColorMapElement c3 = LinearColorMapElement
.create("c3", Color.green, NumberRange.create(100.0f, false,
300.0f, true), 2);
final LinearColorMapElement c4 = LinearColorMapElement
.create("c4", new Color[] { Color.green, Color.red },
NumberRange.create(300.0f, false, 400.0f, true),
NumberRange.create(3, 1000));
final LinearColorMapElement c5 = LinearColorMapElement
.create("c5", new Color[] { Color.red, Color.white },
NumberRange.create(400.0f, false, 1000.0f, true),
NumberRange.create(1001, 2000));
final LinearColorMapElement c6 = LinearColorMapElement
.create("c6", Color.red, 1001.0f, 2001);
final LinearColorMapElement c7 = LinearColorMapElement
.create("nodata", new Color(0, 0, 0, 0), NumberRange.create(
Double.NaN, Double.NaN), 2201);
final LinearColorMap list = new LinearColorMap("",
new LinearColorMapElement[] { c0, c1, c3, c4, c5, c6 },
new LinearColorMapElement[] { c7 });
final ParameterBlockJAI pbj = new ParameterBlockJAI(
RasterClassifier.OPERATION_NAME);
pbj.addSource(image);
pbj.setParameter("Domain1D", list);
final RenderedOp finalimage = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
if (TestData.isInteractiveTest())
RasterSymbolizerTest.visualize(finalimage, "synthetic");
else
finalimage.getTiles();
finalimage.dispose();
}
}
/**
* Building a synthetic image upon a FLOAT sample-model.
*
* @return {@linkplain BufferedImage}
*/
private BufferedImage getSynthetic_Float() {
final int width = 500;
final int height = 500;
final 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 ColorModel cm = new ComponentColorModelJAI(ColorSpace
.getInstance(ColorSpace.CS_GRAY), false, false,
Transparency.OPAQUE, DataBuffer.TYPE_FLOAT);
final BufferedImage image = new BufferedImage(cm, raster, false, null);
return image;
}
/**
* Spearfish test-case.
*
* @throws IOException
*/
@Test
public void spearfish() throws IOException {
// /////////////////////////////////////////////////////////////////////
//
// This test is quite standard since the NoData category specified
// is for NoData values since the input file is a GRASS ascii file
// where the missing values are represented by * which are substituted
// with NaN during reads. The only strange thing that we try here is
// that we map two different classes to the same color with the same
// index.
//
// /////////////////////////////////////////////////////////////////////
final RenderedImage image = getSpearfhisDemo();
for (int i = 0; i < TEST_NUM; i++) {
final LinearColorMapElement c0 = LinearColorMapElement
.create("c0", Color.yellow, NumberRange.create(
Double.NEGATIVE_INFINITY, false, 1100, true), 5);
final LinearColorMapElement c1 = LinearColorMapElement
.create("c2", Color.blue, NumberRange.create(1100.0, false,
1200.0, true), 1);
final LinearColorMapElement c3 = LinearColorMapElement
.create("c3", Color.green, NumberRange.create(1200.0, false,
1400.0, true), 7);
final LinearColorMapElement c4 = LinearColorMapElement
.create("c4", Color.blue, NumberRange.create(1400.0, false,
1600, true), 1);
final LinearColorMapElement c5 = LinearColorMapElement
.create("c4", Color.CYAN, NumberRange.create(1600.0, false,
Double.POSITIVE_INFINITY, true), 11);
final LinearColorMapElement c6 = LinearColorMapElement
.create("nodata", new Color(0, 0, 0, 0), NumberRange.create(
Double.NaN, Double.NaN), 0);
final LinearColorMap list = new LinearColorMap("",
new LinearColorMapElement[] { c0, c1, c3, c4, c5 },
new LinearColorMapElement[] { c6 });
final ParameterBlockJAI pbj = new ParameterBlockJAI(
RasterClassifier.OPERATION_NAME);
pbj.addSource(image);
pbj.setParameter("Domain1D", list);
final RenderedOp finalimage = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
if (TestData.isInteractiveTest())
RasterSymbolizerTest.visualize(finalimage, "spearfish");
else
finalimage.getTiles();
finalimage.dispose();
}
}
/**
* Building an image based on Spearfish data.
*
* @return {@linkplain BufferedImage}
*
* @throws IOException
* @throws FileNotFoundException
*/
private RenderedImage getSpearfhisDemo() throws IOException,
FileNotFoundException {
final AsciiGridsImageReader reader = (AsciiGridsImageReader) new AsciiGridsImageReaderSpi()
.createReaderInstance();
reader.setInput(new FileImageInputStream(TestData.file(this,
"arcgrid/spearfish_dem.arx")));
final RenderedImage image = reader.readAsRenderedImage(0, null);
return image;
}
/**
* SWAN test-case.
*
* @throws IOException
*/
@Test
public void SWAN() throws IOException {
// /////////////////////////////////////////////////////////////////////
//
// This test is interesting since it can be used to simulate the
// case where someone specifies a ColorMap that overlaps with the native
// NoData value. For this SWAN data the NoData value is -9999.0 and no
// NaN which falls right into the first category.
//
// We overcome this problem by simply giving higher priority to the
// NoData category over the other categories when doing a search for
// the right category given a certain value. This force us to
// first evaluate the no data category and then evaluate a possible
// provided overlapping value.
//
// This test is also interesting since we create a color map by
// providing output indexes that are not ordered and also that are not
// all contained in a closed natural interval. As you can notice by
// inspecting the different classes below there is an index, 51, which
// is way outside the range of the others.
//
// /////////////////////////////////////////////////////////////////////
final RenderedImage image = getSWAN();
for (int i = 0; i < TEST_NUM; i++) {
final LinearColorMapElement c0 = LinearColorMapElement
.create("c0", Color.green, NumberRange.create(
Double.NEGATIVE_INFINITY, 0.3), 51);
final LinearColorMapElement c1 = LinearColorMapElement
.create("c2", Color.yellow, NumberRange.create(0.3, false,
0.6, true), 1);
final LinearColorMapElement c1b = LinearColorMapElement
.create("c2", Color.BLACK, NumberRange.create(0.3, false,
0.6, true), 1);
final LinearColorMapElement c1c = LinearColorMapElement
.create("c2", Color.yellow, NumberRange.create(0.3, false,
0.6, true), 1);
assertFalse(c1.equals(c1b));
assertTrue(c1.equals(c1c));
final LinearColorMapElement c3 = LinearColorMapElement
.create("c3", Color.red, NumberRange.create(0.60, false, 0.90,
true), 2);
final LinearColorMapElement c4 = LinearColorMapElement
.create("c4", Color.BLUE, NumberRange.create(0.9, false,
Double.POSITIVE_INFINITY, true), 3);
final LinearColorMapElement nodata = LinearColorMapElement
.create("nodata", new Color(0, 0, 0, 0), NumberRange.create(
-9.0, -9.0), 4);
final LinearColorMap list = new LinearColorMap("testSWAN",
new LinearColorMapElement[] { c0, c1, c3, c4 },
new LinearColorMapElement[] { nodata }, new Color(0,0,0));
assertEquals(list.getSourceDimensions(), 1);
assertEquals(list.getTargetDimensions(), 1);
assertEquals(list.getName().toString(), "testSWAN");
assertNotNull(c0.toString());
final ParameterBlockJAI pbj = new ParameterBlockJAI(
RasterClassifier.OPERATION_NAME);
pbj.addSource(image);
pbj.setParameter("Domain1D", list);
try {
// //
// forcing a bad band selection ...
// //
pbj.setParameter("bandIndex", new Integer(2));
final RenderedOp d = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
d.getTiles();
// we should not be here!
assertTrue(false);
} catch (Exception e) {
// //
// ... ok, Exception wanted!
// //
}
pbj.setParameter("bandIndex", new Integer(0));
final RenderedOp finalImage = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
if (TestData.isInteractiveTest())
RasterSymbolizerTest.visualize(finalImage, "testSWAN1");
else
finalImage.getTiles();
finalImage.dispose();
}
}
/**
* SWAN test-case.
*
* @throws IOException
*/
@Test
public void SWANGAP() throws IOException {
// /////////////////////////////////////////////////////////////////////
//
// This test is interesting since it can be used to simulate the
// case where someone specifies a ColorMap that overlaps with the native
// NoData value. For this SWAN data the NoData value is -9999.0 and no
// NaN which falls right into the first category.
//
// We overcome this problem by simply giving higher priority to the
// NoData category over the other categories when doing a search for
// the right category given a certain value. This force us to
// first evaluate the no data category and then evaluate a possible
// provided overlapping value.
//
// This test is also interesting since we create a color map by
// providing output indexes that are not ordered and also that are not
// all contained in a closed natural interval. As you can notice by
// inspecting the different classes below there is an index, 51, which
// is way outside the range of the others.
//
// /////////////////////////////////////////////////////////////////////
final RenderedImage image = getSWAN();
for (int i = 0; i < TEST_NUM; i++) {
final LinearColorMapElement c0 = LinearColorMapElement
.create("c0", Color.green, NumberRange.create(
Double.NEGATIVE_INFINITY, 0.3), 51);
final LinearColorMapElement c1 = LinearColorMapElement
.create("c2", Color.yellow, NumberRange.create(0.3, false,
0.6, true), 1);
final LinearColorMapElement c3 = LinearColorMapElement
.create("c3", Color.red, NumberRange.create(0.70, false, 0.90,
true), 2);
final LinearColorMapElement c4 = LinearColorMapElement
.create("c4", Color.BLUE, NumberRange.create(0.9, false,
Double.POSITIVE_INFINITY, true), 3);
final LinearColorMapElement nodata = LinearColorMapElement
.create("nodata", Color.red, NumberRange.create(
-9.0, -9.0), 4);
final LinearColorMap list = new LinearColorMap("testSWAN",
new LinearColorMapElement[] { c0, c1, c3, c4 },
new LinearColorMapElement[] { nodata }, new Color(0,0,0,0));
final ParameterBlockJAI pbj = new ParameterBlockJAI(
RasterClassifier.OPERATION_NAME);
pbj.addSource(image);
pbj.setParameter("Domain1D", list);
try {
// //
// forcing a bad band selection ...
// //
pbj.setParameter("bandIndex", new Integer(2));
final RenderedOp d = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
d.getTiles();
// we should not be here!
assertTrue(false);
} catch (Exception e) {
// //
// ... ok, Exception wanted!
// //
}
pbj.setParameter("bandIndex", new Integer(0));
final RenderedOp finalImage = JAI.create(
RasterClassifier.OPERATION_NAME, pbj);
final IndexColorModel icm=(IndexColorModel) finalImage.getColorModel();
assertEquals(icm.getRed(4),255);
assertEquals(icm.getRed(2),255);
if (TestData.isInteractiveTest())
RasterSymbolizerTest.visualize(finalImage, "testSWANGAP");
else
finalImage.getTiles();
finalImage.dispose();
}
}
/**
* Building an image based on SWAN data.
*
* @return {@linkplain BufferedImage}
*
* @throws IOException
* @throws FileNotFoundException
*/
private RenderedImage getSWAN() throws IOException, FileNotFoundException {
final AsciiGridsImageReader reader = (AsciiGridsImageReader) new AsciiGridsImageReaderSpi()
.createReaderInstance();
reader.setInput(new FileImageInputStream(TestData.file(this,
"arcgrid/SWAN_NURC_LigurianSeaL07_HSIGN.asc")));
final RenderedImage image = reader.readAsRenderedImage(0, null);
return image;
}
/**
* NoData only test-case.
*
* @throws IOException
* @throws TransformException
*/
@Test
public void noDataOnly() throws IOException, TransformException {
// /////////////////////////////////////////////////////////////////////
//
// We are covering here a case that can often be verified, i.e. the case
// when only NoData values are known and thus explicitly mapped by the
// user to a defined nodata DomainElement, but not the same for the
// others.
// In such case we want CatrgoryLists automatically map unknown data to
// a Passthrough DomainElement, which identically maps raster data to
// category
// data.
//
// /////////////////////////////////////////////////////////////////////
for (int i = 0; i < TEST_NUM; i++) {
final LinearColorMapElement n0 = LinearColorMapElement
.create("nodata", new Color(0, 0, 0, 0), NumberRange.create(
Double.NaN, Double.NaN), 9999);
final LinearColorMap list = new LinearColorMap("",
new LinearColorMapElement[] { n0 });
double testNum = Math.random();
try{
assertEquals(list.transform(testNum), testNum, 0.0);
assertTrue(false);
}catch (Exception e) {
// TODO: handle exception
}
assertEquals(list.transform(Double.NaN), 9999, 0.0);
}
}
}