| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sis.storage.aggregate; |
| |
| import java.util.List; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| import java.awt.image.DataBufferInt; |
| import java.awt.image.RenderedImage; |
| import org.opengis.util.LocalName; |
| import org.opengis.referencing.datum.PixelInCell; |
| import org.apache.sis.util.iso.Names; |
| import org.apache.sis.referencing.operation.transform.MathTransforms; |
| import org.apache.sis.coverage.SampleDimension; |
| import org.apache.sis.coverage.grid.GridExtent; |
| import org.apache.sis.coverage.grid.GridGeometry; |
| import org.apache.sis.coverage.grid.GridCoverage; |
| import org.apache.sis.coverage.grid.BufferedGridCoverage; |
| import org.apache.sis.coverage.grid.GridCoverageProcessor; |
| import org.apache.sis.storage.DataStoreException; |
| import org.apache.sis.storage.GridCoverageResource; |
| import org.apache.sis.storage.base.MemoryGridResource; |
| |
| // Test dependencies |
| import org.junit.jupiter.api.Test; |
| import static org.junit.jupiter.api.Assertions.*; |
| import org.apache.sis.test.TestCase; |
| import org.apache.sis.referencing.crs.HardCodedCRS; |
| |
| |
| /** |
| * Tests {@link BandAggregateGridResource}. |
| * |
| * @author Alexis Manin (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| */ |
| public final class BandAggregateGridResourceTest extends TestCase { |
| /** |
| * Width and height of images created for tests. |
| */ |
| private static final int WIDTH = 2, HEIGHT = 3; |
| |
| /** |
| * The grid geometry of all coverages created in this test. |
| */ |
| private final GridGeometry domain; |
| |
| /** |
| * The processor to give to {@link BandAggregateGridResource} constructor. |
| */ |
| private final GridCoverageProcessor processor; |
| |
| /** |
| * Whether to hide {@link MemoryGridResource} in order to disable optimizations. |
| */ |
| private boolean opaque; |
| |
| /** |
| * Creates a new test case. |
| */ |
| public BandAggregateGridResourceTest() { |
| opaque = true; |
| processor = new GridCoverageProcessor(); |
| domain = new GridGeometry(new GridExtent(WIDTH, HEIGHT), PixelInCell.CELL_CENTER, |
| MathTransforms.identity(2), HardCodedCRS.WGS84); |
| } |
| |
| /** |
| * Creates a new aggregation of all sample dimensions of all specified grid coverage resources. |
| * The new resource has no identifier and no parent, and uses a default processor with default color model. |
| * |
| * @param sources resources whose bands shall be aggregated, in order. At least one resource must be provided. |
| * @throws DataStoreException if an error occurred while fetching the grid geometry or sample dimensions from a resource. |
| * @throws IllegalGridGeometryException if a grid geometry is not compatible with the others. |
| */ |
| private GridCoverageResource create(final GridCoverageResource... sources) throws DataStoreException { |
| return BandAggregateGridResource.create(null, sources, null, processor); |
| } |
| |
| /** |
| * Tests aggregation of two resources having one band each. |
| * All source coverages share the same grid geometry. |
| * |
| * @throws DataStoreException if an error occurred while reading a resource. |
| */ |
| @Test |
| public void aggregateBandsFromSingleBandSources() throws DataStoreException { |
| final GridCoverageResource first = singleValuePerBand(17); |
| final GridCoverageResource second = singleValuePerBand(23); |
| final var aggregation = create(first, second); |
| |
| assertAllPixelsEqual(aggregation.read(null), 17, 23); |
| assertAllPixelsEqual(aggregation.read(null, 0), 17); |
| assertAllPixelsEqual(aggregation.read(null, 1), 23); |
| } |
| |
| /** |
| * Tests aggregation of three resources having many bands. |
| * All source coverages share the same grid geometry. |
| * |
| * @throws DataStoreException if an error occurred while reading a resource. |
| */ |
| @Test |
| public void aggregateBandsFromMultiBandSources() throws DataStoreException { |
| final GridCoverageResource firstAndSecondBands = singleValuePerBand(101, 102); |
| final GridCoverageResource thirdAndFourthBands = singleValuePerBand(103, 104); |
| final GridCoverageResource fifthAndSixthBands = singleValuePerBand(105, 106); |
| |
| var aggregation = create(firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands); |
| aggregation.getIdentifier().ifPresent(name -> fail("No name provided at creation, but one was returned: " + name)); |
| assertAllPixelsEqual(aggregation.read(null), 101, 102, 103, 104, 105, 106); |
| assertAllPixelsEqual(aggregation.read(null, 1, 2, 4, 5), 102, 103, 105, 106); |
| assertAllPixelsEqual(aggregation.read(null, 3, 4), 104, 105); |
| /* |
| * Test again, but specifying a subset of bands to the constructor. |
| * In addition, band order in one of the 3 coverages is modified. |
| */ |
| final LocalName testName = Names.createLocalName(null, null, "test-name"); |
| aggregation = BandAggregateGridResource.create(null, |
| new GridCoverageResource[] {firstAndSecondBands, thirdAndFourthBands, fifthAndSixthBands}, |
| new int[][] {null, new int[] {1, 0}, new int[] {1}}, processor); |
| if (opaque) { |
| ((BandAggregateGridResource) aggregation).setIdentifier(testName); |
| assertEquals(testName, aggregation.getIdentifier().orElse(null)); |
| } |
| assertAllPixelsEqual(aggregation.read(null), 101, 102, 104, 103, 106); |
| assertAllPixelsEqual(aggregation.read(null, 2, 4), 104, 106); |
| } |
| |
| /** |
| * Tests aggregation of resources using {@link CoverageAggregator}. |
| * |
| * @throws DataStoreException if an error occurred while reading a resource. |
| */ |
| @Test |
| public void usingCoverageAggregator() throws DataStoreException { |
| final GridCoverageResource first = singleValuePerBand(17); |
| final GridCoverageResource second = singleValuePerBand(23); |
| final var aggregator = new CoverageAggregator(null, processor); |
| aggregator.addRangeAggregate(first, second); |
| /* |
| * If the result is not an instance of `MemoryGridResource`, |
| * this is a bug in `BandAggregateGridResource.create(…)`. |
| */ |
| final LocalName testName = Names.createLocalName(null, null, "test-name"); |
| final var aggregation = (GridCoverageResource) aggregator.build(testName); |
| if (opaque) { |
| assertEquals(testName, aggregation.getIdentifier().orElse(null)); |
| } |
| assertAllPixelsEqual(aggregation.read(null), 17, 23); |
| assertAllPixelsEqual(aggregation.read(null, 0), 17); |
| assertAllPixelsEqual(aggregation.read(null, 1), 23); |
| } |
| |
| /** |
| * Tests the shortcut for {@link MemoryGridResource} instances. |
| * {@link BandAggregateGridResource} should apply aggregations directly on the underlying grid coverages. |
| * |
| * @throws DataStoreException if an error occurred while reading a resource. |
| */ |
| @Test |
| public void testMemoryGridResourceShortcut() throws DataStoreException { |
| opaque = false; |
| aggregateBandsFromSingleBandSources(); |
| aggregateBandsFromMultiBandSources(); |
| usingCoverageAggregator(); |
| } |
| |
| /** |
| * Creates a new grid coverage resource with bands having the given values. |
| * The length of the {@code bandValues} array is the number of bands to create. |
| * In a given band <var>b</var>, all pixels have the {@code bandValues[b]}. |
| * |
| * @param bandValues sample values for all pixels. |
| * @return a coverage with an image where all pixels have the specified sample values. |
| */ |
| private GridCoverageResource singleValuePerBand(final int... bandValues) { |
| final var builder = new SampleDimension.Builder(); |
| final List<SampleDimension> samples = IntStream.of(bandValues) |
| .mapToObj(b -> builder.setName("Band with value " + b).build()) |
| .collect(Collectors.toList()); |
| |
| final int numBands = bandValues.length; |
| final int[] data = new int[WIDTH * HEIGHT * numBands]; |
| for (int i=0; i < data.length; i += numBands) { |
| System.arraycopy(bandValues, 0, data, i, numBands); |
| } |
| final var values = new DataBufferInt(data, data.length); |
| final var r = new MemoryGridResource(null, new BufferedGridCoverage(domain, samples, values), null); |
| return opaque ? new OpaqueGridResource(r) : r; |
| } |
| |
| /** |
| * Asserts that all pixels in the rendered image have the expected value. |
| * |
| * @param coverage the coverage for which to verify pixel values. |
| * @param pixel the expected pixel value. |
| */ |
| private static void assertAllPixelsEqual(final GridCoverage coverage, final int... pixel) { |
| final int numBands = pixel.length; |
| final RenderedImage rendering = coverage.render(null); |
| assertEquals(0, rendering.getMinX()); |
| assertEquals(0, rendering.getMinY()); |
| assertEquals(WIDTH, rendering.getWidth()); |
| assertEquals(HEIGHT, rendering.getHeight()); |
| assertEquals(numBands, rendering.getSampleModel().getNumBands()); |
| final int[] expected = new int[WIDTH * HEIGHT * numBands]; |
| for (int i=0; i < expected.length; i += numBands) { |
| System.arraycopy(pixel, 0, expected, i, numBands); |
| } |
| assertArrayEquals(expected, rendering.getData().getPixels(0, 0, WIDTH, HEIGHT, (int[]) null)); |
| } |
| } |