/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014 - 2015, 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.processing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.ROIShape; import javax.media.jai.TileCache; import org.geotools.TestData; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.processing.operation.Mosaic; import org.geotools.coverage.processing.operation.Mosaic.GridGeometryPolicy; import org.geotools.data.WorldFileReader; import org.geotools.factory.GeoTools; import org.geotools.factory.Hints; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.Envelope2D; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.referencing.operation.builder.GridToEnvelopeMapper; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.resources.image.ImageUtilities; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.opengis.geometry.DirectPosition; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.parameter.ParameterNotFoundException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; import com.sun.media.jai.util.CacheDiagnostics; import com.sun.media.jai.util.SunTileCache; import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader; import it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReaderSpi; /** * This class tests the {@link Mosaic} operation. The tests ensures that the final {@link GridCoverage2D} created contains the union of the input * bounding box or is equal to that provided by the external {@link GridGeometry2D}. Also the tests check if the output {@link GridCoverage2D} * resolution is equal to that imposed in input with the {@link GridGeometryPolicy}. * * * @author Nicola Lagomarsini GesoSolutions S.A.S. * */ public class MosaicTest extends GridProcessingTestBase { private static final GridCoverageFactory GRID_COVERAGE_FACTORY = CoverageFactoryFinder.getGridCoverageFactory(null); /** Tolerance value for the double comparison */ private static final double TOLERANCE = 0.01d; /** Tile size used for testing the Mosaic*/ private static final int TILE_SIZE = 10; /** * WKT used for testing that the operation throws an exception when the input {@link GridCoverage2D}s does not have the same * {@link CoordinateReferenceSystem}. */ private final static String GOOGLE_MERCATOR_WKT = "PROJCS[\"WGS 84 / Pseudo-Mercator\"," + "GEOGCS[\"Popular Visualisation CRS\"," + "DATUM[\"Popular_Visualisation_Datum\"," + "SPHEROID[\"Popular Visualisation Sphere\",6378137,0," + "AUTHORITY[\"EPSG\",\"7059\"]]," + "TOWGS84[0,0,0,0,0,0,0]," + "AUTHORITY[\"EPSG\",\"6055\"]]," + "PRIMEM[\"Greenwich\",0," + "AUTHORITY[\"EPSG\",\"8901\"]]," + "UNIT[\"degree\",0.01745329251994328," + "AUTHORITY[\"EPSG\",\"9122\"]]," + "AUTHORITY[\"EPSG\",\"4055\"]]," + "UNIT[\"metre\",1," + "AUTHORITY[\"EPSG\",\"9001\"]]," + "PROJECTION[\"Mercator_1SP\"]," + "PARAMETER[\"central_meridian\",0]," + "PARAMETER[\"scale_factor\",1]," + "PARAMETER[\"false_easting\",0]," + "PARAMETER[\"false_northing\",0]," + "AUTHORITY[\"EPSG\",\"3785\"]," + "AXIS[\"X\",EAST]," + "AXIS[\"Y\",NORTH]]"; /** First Coverage used */ private static GridCoverage2D coverage1; /** Second Coverage used. It is equal to the first one but translated on the X axis. */ private static GridCoverage2D coverage2; /** Third Coverage used. It is equal to the first one but contains an alpha band */ private static GridCoverage2D coverage3; /** Fourth Coverage used. It is equal to the second one but contains an alpha band */ private static GridCoverage2D coverage4; /** * The processor to be used for all tests. */ private static final CoverageProcessor processor = CoverageProcessor.getInstance(GeoTools .getDefaultHints()); //private static final Mosaic MOSAIC = (Mosaic) processor.getOperation("Mosaic"); // Static method used for preparing the input data. @BeforeClass public static void setup() throws FileNotFoundException, IOException { TestData.unzipFile(MosaicTest.class, "sampleData.zip"); coverage1 = readInputFile("sampleData"); coverage2 = readInputFile("sampleData2"); coverage3 = readInputFile("sampleData3"); coverage4 = readInputFile("sampleData4"); } /** * Private method for reading the input file. * * @param filename * @return * @throws FileNotFoundException * @throws IOException */ private static GridCoverage2D readInputFile(String filename) throws FileNotFoundException, IOException { final File tiff = TestData.file(MosaicTest.class, filename + ".tif"); final File tfw = TestData.file(MosaicTest.class, filename + ".tfw"); final TIFFImageReader reader = (it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader) new TIFFImageReaderSpi() .createReaderInstance(); reader.setInput(ImageIO.createImageInputStream(tiff)); final BufferedImage image = reader.read(0); final MathTransform transform = new WorldFileReader(tfw).getTransform(); final GridCoverage2D coverage2D = GRID_COVERAGE_FACTORY .create("coverage" + filename, image, new GridGeometry2D(new GridEnvelope2D(PlanarImage.wrapRenderedImage(image) .getBounds()), transform, DefaultGeographicCRS.WGS84), null, null, null); return coverage2D; } // Simple test which mosaics two input coverages without any additional parameter @Test public void testMosaicSimple() { GridCoverage2D mosaic = simpleMosaic(coverage1, coverage2); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } @Test public void testCacheCleanup() { // make sure the tile cache is empty TileCache tc = JAI.getDefaultInstance().getTileCache(); tc.flush(); testMosaicWithAnotherNoData(); // the cleanup was full, no leftovers assertEquals(0, ((CacheDiagnostics) tc).getCacheTileCount()); assertEquals(0, ((CacheDiagnostics) tc).getCacheMemoryUsed()); } private GridCoverage2D simpleMosaic(GridCoverage2D coverage1, GridCoverage2D coverage2) { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); sources.add(coverage2); // Setting of the sources param.parameter("Sources").setValue(sources); // RenderingHints Hints hints = new Hints(); // Ensure No Layout is set Assert.assertTrue(!hints.containsKey(JAI.KEY_IMAGE_LAYOUT)); // Add a fake Layout for the operation ImageLayout il = new ImageLayout(); hints.put(JAI.KEY_IMAGE_LAYOUT, il); il.setTileHeight(TILE_SIZE); il.setTileWidth(TILE_SIZE); // Mosaic operation GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param, hints); // Check that the final GridCoverage BoundingBox is equal to the union of the separate coverages bounding box Envelope2D expected = coverage1.getEnvelope2D(); expected.include(coverage2.getEnvelope2D()); // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that Tiling has been defined correctly RenderedImage renderedImage = mosaic.getRenderedImage(); assertEquals(TILE_SIZE, renderedImage.getTileHeight()); assertEquals(TILE_SIZE, renderedImage.getTileWidth()); // Check that the final Coverage resolution is equal to that of the first coverage double initialRes = calculateResolution(coverage1); double finalRes = calculateResolution(mosaic); double percentual = Math.abs(initialRes - finalRes) / initialRes; Assert.assertTrue(percentual < TOLERANCE); // Check that on the center of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getCenterX(), actual.getCenterY()); double nodata = CoverageUtilities.getBackgroundValues(coverage1)[0]; double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Ensure the Layout is already present after the mosaic Assert.assertTrue(hints.containsKey(JAI.KEY_IMAGE_LAYOUT)); // Ensure no additional bound parameter is set ImageLayout layout = (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT); Assert.assertTrue(!layout.isValid(ImageLayout.MIN_X_MASK)); Assert.assertTrue(!layout.isValid(ImageLayout.MIN_Y_MASK)); Assert.assertTrue(!layout.isValid(ImageLayout.WIDTH_MASK)); Assert.assertTrue(!layout.isValid(ImageLayout.HEIGHT_MASK)); Assert.assertTrue(layout.isValid(ImageLayout.TILE_HEIGHT_MASK)); Assert.assertTrue(layout.isValid(ImageLayout.TILE_WIDTH_MASK)); return mosaic; } @Test public void testMosaicSimpleWithNullROI() { // mosaic the two coverages, one with a ROI, the other with none (used to blow up) GridCoverage2D mosaic = simpleMosaic(getCoverageWithFullROI(coverage1), coverage2); // check we have a ROI and it's the union of the two ROI roi = CoverageUtilities.getROIProperty(mosaic); assertNotNull(roi); Rectangle bounds1 = getImageBounds(coverage1.getRenderedImage()); Rectangle bounds2 = getImageBounds(coverage2.getRenderedImage()); Rectangle boundsMosaic = bounds1.union(bounds2); assertEquals(boundsMosaic, roi.getBounds()); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } private GridCoverage2D getCoverageWithFullROI(GridCoverage2D coverage) { Map<String, Object> properties = new HashMap<>((coverage.getProperties() != null) ? coverage.getProperties() : Collections.emptyMap()); RenderedImage ri = coverage.getRenderedImage(); ROIShape roi = new ROIShape(getImageBounds(ri)); CoverageUtilities.setROIProperty(properties, roi); GridCoverage2D coverageWithRoi = GRID_COVERAGE_FACTORY.create(coverage.getName(), ri, coverage.getEnvelope(), coverage.getSampleDimensions(), null, properties ); return coverageWithRoi; } private Rectangle getImageBounds(RenderedImage ri) { return new Rectangle(ri.getMinX(), ri.getMinY(), ri.getWidth(), ri.getHeight()); } // Simple test which tries to mosaic an input coverage without settings sources parameter @Test(expected=ParameterNotFoundException.class) public void testMosaicNoSource() { /* * Getting parameters */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Setting source0 parameter param.parameter("source0").setValue(coverage1); } // Simple test which mosaics two input coverages with a different value for the output nodata @Test public void testMosaicWithAnotherNoData() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); sources.add(coverage2); // Setting of the sources param.parameter("Sources").setValue(sources); // Setting of the nodata double nodata = -9999; param.parameter(Mosaic.OUTNODATA_NAME).setValue(new double[] { nodata }); // Mosaic GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param); // Check that the final GridCoverage BoundingBox is equal to the union of the separate coverages bounding box Envelope2D expected = coverage1.getEnvelope2D(); expected.include(coverage2.getEnvelope2D()); // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that the final Coverage resolution is equal to that of the first coverage double initialRes = calculateResolution(coverage1); double finalRes = calculateResolution(mosaic); double percentual = Math.abs(initialRes - finalRes) / initialRes; Assert.assertTrue(percentual < TOLERANCE); // Check that on the center of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getCenterX(), actual.getCenterY()); double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } // Test which mosaics two input coverages with alpha band @Test public void testMosaicWithAlpha() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage3); sources.add(coverage4); // Setting of the sources param.parameter("Sources").setValue(sources); // Mosaic GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param); // Check that the final Coverage Bands are 2 Assert.assertEquals(2, mosaic.getNumSampleDimensions()); // Check that the final GridCoverage BoundingBox is equal to the union of the separate coverages bounding box Envelope2D expected = coverage3.getEnvelope2D(); expected.include(coverage4.getEnvelope2D()); // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that the final Coverage resolution is equal to that of the first coverage double initialRes = calculateResolution(coverage3); double finalRes = calculateResolution(mosaic); double percentual = Math.abs(initialRes - finalRes) / initialRes; Assert.assertTrue(percentual < TOLERANCE); // Check that on the center of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getCenterX(), actual.getCenterY()); double nodata = CoverageUtilities.getBackgroundValues(coverage1)[0]; double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } // Test which mosaics two input coverages and resamples them by using the resolution of an external GridGeometry2D @Test public void testMosaicExternalGeometry() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); sources.add(coverage2); // Setting of the sources param.parameter("Sources").setValue(sources); // Initial Bounding box Envelope2D startBBOX = coverage1.getEnvelope2D(); startBBOX.include(coverage2.getEnvelope2D()); Envelope2D expected = new Envelope2D(startBBOX); Point2D pt = new Point2D.Double(startBBOX.getMaxX() + 1, startBBOX.getMaxY() + 1); expected.add(pt); // External GridGeometry GridGeometry2D ggStart = new GridGeometry2D(PixelInCell.CELL_CORNER, coverage1 .getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT), expected, GeoTools.getDefaultHints()); param.parameter("geometry").setValue(ggStart); // Mosaic GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param); // Check that the final GridCoverage BoundingBox is equal to the bounding box provided in input // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that the final Coverage resolution is equal to that of the first coverage double initialRes = calculateResolution(coverage1); double finalRes = calculateResolution(mosaic); Assert.assertEquals(initialRes, finalRes, TOLERANCE); // Check that on the Upper Right pixel of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMaxY() - finalRes); double nodata = CoverageUtilities.getBackgroundValues(coverage1)[0]; double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } // Test which mosaics two input coverages and tries to impose a null GridGeometry. An exception will be thrown @Test(expected = CoverageProcessingException.class) public void testMosaicNoExternalGeometry() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); sources.add(coverage2); // Setting of the sources param.parameter("Sources").setValue(sources); param.parameter(Mosaic.POLICY).setValue("external"); // Mosaic processor.doOperation(param); } // Test which mosaics two input coverages and creates a final GridCoverage with the worst resolution between those of the input GridCoverages @Test public void testMosaicCoarseResolution() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); // Resampling of the second Coverage to an higher resolution ParameterValueGroup paramResampling = processor.getOperation("resample").getParameters(); paramResampling.parameter("Source").setValue(coverage2); GridEnvelope2D gridRange = coverage2.getGridGeometry().getGridRange2D(); gridRange.add(gridRange.getMaxX() + 100, gridRange.getMaxY() + 100); GridGeometry2D ggNew = new GridGeometry2D(gridRange, coverage2.getEnvelope()); paramResampling.parameter("GridGeometry").setValue(ggNew); GridCoverage2D resampled = (GridCoverage2D) processor.doOperation(paramResampling); sources.add(resampled); // Setting of the sources param.parameter("Sources").setValue(sources); param.parameter(Mosaic.POLICY).setValue("coarse"); // Mosaic GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param); // Check that the final GridCoverage BoundingBox is equal to the union of the separate coverages bounding box Envelope2D expected = coverage1.getEnvelope2D(); expected.include(resampled.getEnvelope2D()); // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that the final Coverage resolution is equal to that of the first coverage double initialRes = calculateResolution(coverage1); double finalRes = calculateResolution(mosaic); double percentual = Math.abs(initialRes - finalRes) / initialRes; Assert.assertTrue(percentual < TOLERANCE); // Check that on the center of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getCenterX(), actual.getCenterY()); double nodata = CoverageUtilities.getBackgroundValues(coverage1)[0]; double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); resampled.dispose(true); disposeCoveragePlanarImage(mosaic); disposeCoveragePlanarImage(resampled); } // Test which mosaics two input coverages and creates a final GridCoverage with the best resolution between those of the input GridCoverages @Test public void testMosaicFineResolution() { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); // Resampling of the second Coverage to an higher resolution ParameterValueGroup paramResampling = processor.getOperation("resample").getParameters(); paramResampling.parameter("Source").setValue(coverage2); GridEnvelope2D gridRange = coverage2.getGridGeometry().getGridRange2D(); gridRange.add(gridRange.getMaxX() + 100, gridRange.getMaxY() + 100); GridGeometry2D ggNew = new GridGeometry2D(gridRange, coverage2.getEnvelope()); paramResampling.parameter("GridGeometry").setValue(ggNew); GridCoverage2D resampled = (GridCoverage2D) processor.doOperation(paramResampling); sources.add(resampled); // Setting of the sources param.parameter("Sources").setValue(sources); param.parameter(Mosaic.POLICY).setValue("fine"); // Mosaic GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param); // Check that the final GridCoverage BoundingBox is equal to the union of the separate coverages bounding box Envelope2D expected = coverage1.getEnvelope2D(); expected.include(resampled.getEnvelope2D()); // Mosaic Envelope Envelope2D actual = mosaic.getEnvelope2D(); // Check the same Bounding Box assertEqualBBOX(expected, actual); // Check that the final Coverage resolution is equal to that of the second coverage double initialRes = calculateResolution(resampled); double finalRes = calculateResolution(mosaic); double percentual = Math.abs(initialRes - finalRes) / initialRes; Assert.assertTrue(percentual < TOLERANCE); // Check that on the center of the image there are nodata DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getCenterX(), actual.getCenterY()); double nodata = CoverageUtilities.getBackgroundValues(coverage1)[0]; double result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), actual.getMinX() + finalRes, actual.getMinY() + finalRes); result = ((int[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); resampled.dispose(true); disposeCoveragePlanarImage(mosaic); disposeCoveragePlanarImage(resampled); } // Test which mosaics two input coverages with different CRS. An exception will be thrown @Test(expected = CoverageProcessingException.class) public void testWrongCRS() throws InvalidParameterValueException, ParameterNotFoundException, FactoryException { /* * Do the crop without conserving the envelope. */ ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); sources.add(coverage1); // Reprojection of the second Coverage ParameterValueGroup paramReprojection = processor.getOperation("resample").getParameters(); paramReprojection.parameter("Source").setValue(coverage2); paramReprojection.parameter("CoordinateReferenceSystem").setValue( CRS.parseWKT(GOOGLE_MERCATOR_WKT)); GridCoverage2D resampled = (GridCoverage2D) processor.doOperation(paramReprojection); sources.add(resampled); // Setting of the sources param.parameter("Sources").setValue(sources); // Mosaic processor.doOperation(param); } // Test which takes an input file, extracts two coverages from it, shifts the first on the right of the second one and then mosaics them. @Test public void testWorldFile() throws FileNotFoundException, IOException { // read the coverage GridCoverage2D test = readInputFile("sample0"); // Envelope for the first half of the image ReferencedEnvelope re1 = new ReferencedEnvelope(10, 180, -90, 90, DefaultGeographicCRS.WGS84); // Coverage crop for extracting the first half of the image GridCoverage2D c1 = crop(test, new GeneralEnvelope(re1)); // Envelope for the second half of the image ReferencedEnvelope re2 = new ReferencedEnvelope(-180, -10, -90, 90, DefaultGeographicCRS.WGS84); // Coverage crop for extracting the second half of the image GridCoverage2D c2 = crop(test, new GeneralEnvelope(re2)); // Shift the first image on the right ReferencedEnvelope re3 = new ReferencedEnvelope(180, 350, -90, 90, DefaultGeographicCRS.WGS84); GridCoverage2D shifted = new GridCoverageFactory().create(c2.getName(), c2.getRenderedImage(), re3); // Envelope containing the bounding box for the two images ReferencedEnvelope reUnion = new ReferencedEnvelope(10, 350, -90, 90, DefaultGeographicCRS.WGS84); // Mosaic operation GridCoverage2D mosaic = mosaic(sortCoverages(Arrays.asList(c1, shifted)), new GeneralEnvelope(reUnion), new Hints()); // Ensure the mosaic Bounding box is equal to that expected Envelope2D expected = new Envelope2D(reUnion); assertEqualBBOX(expected, mosaic.getEnvelope2D()); // Check that the final Coverage resolution is equal to that of the first coverage double finalRes = calculateResolution(mosaic); // Check that on the center of the image there is valid data DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), expected.getCenterX(), expected.getCenterY()); double nodata = 0; double result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Check that on the Upper Left border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), expected.getMinX() + finalRes, expected.getMinY() + finalRes); result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Check that on the Upper Right border pixel there is valid data point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), expected.getMaxX() - finalRes, expected.getMinY() + finalRes); result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } // Test which takes an input file, extracts 4 coverages from it, shifts them in order to created a replication. @Test public void testWorldFile2() throws FileNotFoundException, IOException { // read the two coverages GridEnvelope2D gridRange = new GridEnvelope2D(0, 0, 400, 200); ReferencedEnvelope re = new ReferencedEnvelope(-180, 180, -85, 85, DefaultGeographicCRS.WGS84); GridGeometry2D gg = new GridGeometry2D(gridRange, re); GridCoverage2D cStart = readInputFile("sample0"); GridCoverage2D cCrop = crop(cStart, new GeneralEnvelope(re)); // Resampling of the Coverage to the defined resolution ParameterValueGroup paramResampling = processor.getOperation("resample").getParameters(); paramResampling.parameter("Source").setValue(cCrop); paramResampling.parameter("GridGeometry").setValue(gg); GridCoverage2D c = (GridCoverage2D) processor.doOperation(paramResampling); // first shifted ReferencedEnvelope re2 = new ReferencedEnvelope(-540, -180, -85, 85, DefaultGeographicCRS.WGS84); GridCoverage2D c2 = new GridCoverageFactory() .create(c.getName(), c.getRenderedImage(), re2); // second shifted ReferencedEnvelope re3 = new ReferencedEnvelope(180, 540, -85, 85, DefaultGeographicCRS.WGS84); GridCoverage2D c3 = new GridCoverageFactory() .create(c.getName(), c.getRenderedImage(), re3); // third shifted ReferencedEnvelope re4 = new ReferencedEnvelope(-540, -900, -85, 85, DefaultGeographicCRS.WGS84); GridCoverage2D c4 = new GridCoverageFactory() .create(c.getName(), c.getRenderedImage(), re4); ReferencedEnvelope reUnion = new ReferencedEnvelope(-900, 540, -85, 85, DefaultGeographicCRS.WGS84); List<GridCoverage2D> sorted = sortCoverages(Arrays.asList(c4, c2, c, c3)); GridCoverage2D mosaic = mosaic(sorted, new GeneralEnvelope(reUnion), new Hints()); // Ensure the mosaic Bounding box is equal to that expected Envelope2D expected = new Envelope2D(reUnion); assertEqualBBOX(expected, mosaic.getEnvelope2D()); // Calculate the mosaic resolution double res = calculateResolution(mosaic); // Ensure that no black lines are present on the border between the input images // Check that on the center of the image there is valid data DirectPosition point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), -540, -84); double nodata = 0; double result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), -540 - res, -84); result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); point = new DirectPosition2D(mosaic.getCoordinateReferenceSystem(), -540 + res, -84); result = ((byte[]) mosaic.evaluate(point))[0]; Assert.assertNotEquals(nodata, result, TOLERANCE); // Coverage and RenderedImage disposal mosaic.dispose(true); disposeCoveragePlanarImage(mosaic); } @Test public void testPaletted() throws IOException { ParameterValueGroup param = processor.getOperation("Mosaic").getParameters(); // Creation of a List of the input Sources List<GridCoverage2D> sources = new ArrayList<GridCoverage2D>(2); GridCoverage2D world = readWorldPaletted(); sources.add(world); ReferencedEnvelope reShifted = new ReferencedEnvelope(-360, -180, -90, 90, DefaultGeographicCRS.WGS84); GridCoverage2D shifted = new GridCoverageFactory().create(world.getName(), world.getRenderedImage(), reShifted); sources.add(shifted); param.parameter("Sources").setValue(sources); // Mosaic simulating a hints set that contains index color model expansion Hints hints = new Hints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE); GridCoverage2D mosaic = (GridCoverage2D) processor.doOperation(param, hints); assertNotNull(mosaic); assertEquals(3, mosaic.getRenderedImage().getSampleModel().getNumBands()); } private GridCoverage2D readWorldPaletted() throws IOException { File tiff = TestData.copy(this, "geotiff/worldPalette.tiff"); final TIFFImageReader reader = (it.geosolutions.imageioimpl.plugins.tiff.TIFFImageReader) new TIFFImageReaderSpi() .createReaderInstance(); reader.setInput(ImageIO.createImageInputStream(tiff)); final BufferedImage image = reader.read(0); final MathTransform transform = new GridToEnvelopeMapper(new GridEnvelope2D(0, 0, image.getWidth(), image.getHeight()), new ReferencedEnvelope(-180, 180, -90, 90, DefaultGeographicCRS.WGS84)).createTransform(); final GridCoverage2D coverage2D = GRID_COVERAGE_FACTORY .create("world", image, new GridGeometry2D(new GridEnvelope2D(PlanarImage.wrapRenderedImage(image) .getBounds()), transform, DefaultGeographicCRS.WGS84), null, null, null); return coverage2D; } @AfterClass public static void finalStep() { // Coverage and RenderedImage disposal coverage1.dispose(true); coverage2.dispose(true); disposeCoveragePlanarImage(coverage1); disposeCoveragePlanarImage(coverage2); } /** * Method for disposing the {@link RenderedImage} chain of the {@link GridCoverage2D} * * @param coverage */ private static void disposeCoveragePlanarImage(GridCoverage2D coverage) { ImageUtilities.disposePlanarImageChain(PlanarImage.wrapRenderedImage(coverage .getRenderedImage())); } /** * Method for calculating the resolution of the input {@link GridCoverage2D} * * @param coverage * @return */ private static double calculateResolution(GridCoverage2D coverage) { GridGeometry2D gg2D = coverage.getGridGeometry(); double envW = gg2D.getEnvelope2D().width; double gridW = gg2D.getGridRange2D().width; double res = envW / gridW; return res; } /** * Method which ensures that the two {@link Envelope2D} objects are equals. * * @param expected * @param actual */ private void assertEqualBBOX(Envelope2D expected, Envelope2D actual) { Assert.assertEquals(expected.getX(), actual.getX(), TOLERANCE); Assert.assertEquals(expected.getY(), actual.getY(), TOLERANCE); Assert.assertEquals(expected.getHeight(), actual.getHeight(), TOLERANCE); Assert.assertEquals(expected.getWidth(), actual.getWidth(), TOLERANCE); } /** * Method for cropping the input coverage with the defined envelope. * * @param gc * @param envelope * @return */ private GridCoverage2D crop(GridCoverage2D gc, GeneralEnvelope envelope) { final GeneralEnvelope oldEnvelope = (GeneralEnvelope) gc.getEnvelope(); // intersect the envelopes in order to prepare for cropping the coverage // down to the neded resolution final GeneralEnvelope intersectionEnvelope = new GeneralEnvelope(envelope); intersectionEnvelope.setCoordinateReferenceSystem(envelope.getCoordinateReferenceSystem()); intersectionEnvelope.intersect((GeneralEnvelope) oldEnvelope); // Do we have something to show? After the crop I could get a null // coverage which would mean nothing to show. if (intersectionEnvelope.isEmpty()) { return null; } // crop final ParameterValueGroup param = (ParameterValueGroup) processor .getOperation("CoverageCrop").getParameters().clone(); param.parameter("source").setValue(gc); param.parameter("Envelope").setValue(intersectionEnvelope); return (GridCoverage2D) processor.doOperation(param, GeoTools.getDefaultHints()); } /** * Method for mosaicking two input images and setting the final BoundingBox * @param coverages * @param renderingEnvelope * @param hints * @return */ private GridCoverage2D mosaic(List<GridCoverage2D> coverages, GeneralEnvelope renderingEnvelope, Hints hints) { // setup the grid geometry try { // find the intersection between the target envelope and the coverages one ReferencedEnvelope targetEnvelope = ReferencedEnvelope.reference(renderingEnvelope); ReferencedEnvelope coveragesEnvelope = null; for (GridCoverage2D coverage : coverages) { ReferencedEnvelope re = ReferencedEnvelope.reference(coverage.getEnvelope2D()); if (coveragesEnvelope == null) { coveragesEnvelope = re; } else { coveragesEnvelope.expandToInclude(re); } } targetEnvelope = new ReferencedEnvelope(targetEnvelope.intersection(coveragesEnvelope), renderingEnvelope.getCoordinateReferenceSystem()); if (targetEnvelope.isEmpty() || targetEnvelope.isNull()) { return null; } MathTransform2D mt = coverages.get(0).getGridGeometry().getCRSToGrid2D(); Rectangle rasterSpaceEnvelope; rasterSpaceEnvelope = CRS.transform(mt, targetEnvelope).toRectangle2D().getBounds(); GridEnvelope2D gridRange = new GridEnvelope2D(rasterSpaceEnvelope); GridGeometry2D gridGeometry = new GridGeometry2D(gridRange, targetEnvelope); // mosaic final ParameterValueGroup param = processor.getOperation("Mosaic").getParameters().clone(); param.parameter("sources").setValue(coverages); param.parameter("geometry").setValue(gridGeometry); return (GridCoverage2D) ((Mosaic)processor.getOperation("Mosaic")).doOperation(param, hints); } catch (Exception e) { throw new RuntimeException("Failed to mosaic the input coverages", e); } } private List<GridCoverage2D> sortCoverages(List<GridCoverage2D> coverages) { Collections.sort(coverages, new Comparator<GridCoverage2D>() { @Override public int compare(GridCoverage2D o1, GridCoverage2D o2) { double minx1 = o1.getEnvelope().getMinimum(0); double minx2 = o2.getEnvelope().getMinimum(0); if (minx1 == minx2) { double maxy1 = o1.getEnvelope().getMaximum(1); double maxy2 = o2.getEnvelope().getMaximum(1); return compareDoubles(maxy1, maxy2); } else { return compareDoubles(minx1, minx2); } } private int compareDoubles(double maxy1, double maxy2) { if (maxy1 == maxy2) { return 0; } else { return (int) Math.signum(maxy1 - maxy2); } } }); return coverages; } }