This is an automated email from the ASF dual-hosted git repository. desruisseaux pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 85172cf8d6d2f48dca9d5c78a8251a71ec0d917a Author: Martin Desruisseaux <[email protected]> AuthorDate: Fri Aug 8 17:57:21 2025 +0200 Add a clip operation in `GridCoverageProcessor` working on grid coordinates. --- .../coverage/grid/BandAggregateGridCoverage.java | 2 +- .../sis/coverage/grid/ClippedGridCoverage.java | 193 ++++++++++++++++++++ .../apache/sis/coverage/grid/DefaultEvaluator.java | 3 +- .../sis/coverage/grid/DerivedGridCoverage.java | 2 +- .../org/apache/sis/coverage/grid/GridCoverage.java | 4 +- .../apache/sis/coverage/grid/GridCoverage2D.java | 2 +- .../sis/coverage/grid/GridCoverageProcessor.java | 34 +++- .../sis/coverage/grid/TranslatedGridCoverage.java | 9 +- .../sis/coverage/grid/ClippedGridCoverageTest.java | 202 +++++++++++++++++++++ .../coverage/grid/TranslatedGridCoverageTest.java | 4 +- netbeans-project/nbproject/project.xml | 1 + 11 files changed, 440 insertions(+), 16 deletions(-) diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java index c51ab5cc97..52fbe4393e 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/BandAggregateGridCoverage.java @@ -299,7 +299,7 @@ final class BandAggregateGridCoverage extends GridCoverage { offset += values.length; } else { for (int b : bands) { - values[offset++] = values[b]; + aggregate[offset++] = values[b]; } } } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java new file mode 100644 index 0000000000..a81b2c6286 --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/ClippedGridCoverage.java @@ -0,0 +1,193 @@ +/* + * 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.coverage.grid; + +import java.util.Map; +import java.awt.image.RenderedImage; +import org.opengis.referencing.operation.TransformException; +import org.apache.sis.util.CorruptedObjectException; +import org.apache.sis.image.PlanarImage; +import org.apache.sis.image.privy.ReshapedImage; + + +/** + * A grid coverage for a subset of the data as the source coverage. + * + * @author Martin Desruisseaux (Geomatys) + */ +final class ClippedGridCoverage extends DerivedGridCoverage { + /** + * The coordinates of the slice, or {@code null} if same as the source coverage. + * + * @see #evaluator() + */ + private final Map<Integer,Long> defaultSlice; + + /** + * Constructs a new grid coverage which will delegate the rendering operation to the given source. + * This coverage will take the same sample dimensions as the source. + * + * @param source the source to which to delegate rendering operations. + * @param domain the grid extent, CRS and conversion from cell indices to CRS. + */ + private ClippedGridCoverage(final GridCoverage source, final GridGeometry domain) { + super(source, domain); + final Map<Integer,Long> c = domain.getExtent().getSliceCoordinates(); + if (c.equals(source.getGridGeometry().getExtent().getSliceCoordinates())) { + defaultSlice = null; + } else { + defaultSlice = Map.copyOf(c); + } + } + + /** + * Returns a grid coverage clipped to the given extent. + * + * @param source the source to which to delegate rendering operations. + * @param clip the clip to apply in units of source grid coordinates. + * @return the clipped coverage. May be the {@code source} returned as-is. + * @throws IncompleteGridGeometryException if the given coverage has no grid extent. + * @throws DisjointExtentException if the given extent does not intersect the given coverage. + * @throws IllegalArgumentException if axes of the given extent are inconsistent with the axes of the grid of the given coverage. + */ + static GridCoverage create(GridCoverage source, final GridExtent clip, final boolean allowSourceReplacement) { + GridGeometry gridGeometry = source.getGridGeometry(); + GridExtent extent = gridGeometry.getExtent(); + if (extent == (extent = extent.intersect(clip))) { + return source; + } + if (allowSourceReplacement) { + while (source instanceof ClippedGridCoverage) { + source = ((ClippedGridCoverage) source).source; + if (extent.equals(source.gridGeometry.extent)) { + return source; + } + } + } + try { + gridGeometry = new GridGeometry(gridGeometry, extent, null); + } catch (TransformException e) { + // Unable to transform an envelope which was successfully transformed before. + // If it happens, assume that something wrong happened with the objects. + throw new CorruptedObjectException(e); + } + return new ClippedGridCoverage(source, gridGeometry); + } + + /** + * Returns a grid coverage that contains real values or sample values, depending if {@code converted} + * is {@code true} or {@code false} respectively. This method delegates to the source and wraps the + * result in a {@link ClippedGridCoverage} with the same extent. + */ + @Override + protected final GridCoverage createConvertedValues(final boolean converted) { + final GridCoverage c = source.forConvertedValues(converted); + return (c == source) ? this : new ClippedGridCoverage(c, gridGeometry); + } + + /** + * Returns a two-dimensional slice of grid data as a rendered image. + */ + @Override + public RenderedImage render(GridExtent sliceExtent) { + final GridExtent clipped; + if (sliceExtent == null) { + clipped = sliceExtent = gridGeometry.extent; + } else { + clipped = sliceExtent.intersect(gridGeometry.extent); + } + final RenderedImage image = source.render(clipped); + /* + * After `render(…)` execution, the (minX, minY) image coordinates are the differences + * between the extent that we requested and what we got. If the clipped extent that we + * specified in above method call has an origin different than the user-supplied extent, + * we need to adjust. + */ + if (clipped != sliceExtent) { // Slight optimization for a common case. + long any = 0; + final long[] translation = new long[clipped.getDimension()]; + for (int i=0; i<translation.length; i++) { + any |= (translation[i] = Math.subtractExact(clipped.getLow(i), sliceExtent.getLow(i))); + } + if (any != 0) { + final Object property = image.getProperty(PlanarImage.XY_DIMENSIONS_KEY); + final int[] gridDimensions; + if (property instanceof int[]) { + gridDimensions = (int[]) property; + } else { + gridDimensions = clipped.getSubspaceDimensions(GridCoverage2D.BIDIMENSIONAL); + } + final var t = new ReshapedImage(image, translation[gridDimensions[0]], translation[gridDimensions[1]]); + return t.isIdentity() ? t.source : t; + } + } + return image; + } + + /** + * Creates a new function for computing or interpolating sample values at given locations. + * That function accepts {@link DirectPosition} in arbitrary Coordinate Reference System. + * If a given position contains coordinate values for dimensions that were sliced by this + * {@code ClippedGridCoverage}, the coordinates of the specified position will have precedence + * over the grid extent that were specified in the {@link ClippedGridCoverage} constructor. + */ + @Override + public Evaluator evaluator() { + Evaluator evaluator = source.evaluator(); + if (defaultSlice != null) { + evaluator = new SliceEvaluator(evaluator); + } + return evaluator; + } + + /** + * Implementation of the evaluator returned by {@link #evaluator()}. + * This evaluator delegates to the source evaluator with default slices + * initialized to the values specified by {@link ClippedGridCoverage}. + */ + private final class SliceEvaluator extends EvaluatorWrapper { + /** + * Creates a new evaluator wrapping the given source coverage evaluator. + * + * @param source an evaluator newly created from the source coverage. + */ + SliceEvaluator(final GridCoverage.Evaluator source) { + super(source); + super.setDefaultSlice(defaultSlice); + } + + /** + * Returns the coverage from which this evaluator is fetching sample values. + * This is the coverage on which the {@link ClippedGridCoverage#evaluator()} + * method has been invoked. + */ + @Override + public GridCoverage getCoverage() { + return ClippedGridCoverage.this; + } + + /** + * Sets the default slice where to perform evaluation when the points do not have enough dimensions. + * + * @throws IllegalArgumentException if the map contains an illegal dimension or grid coordinate value. + */ + @Override + public void setDefaultSlice(final Map<Integer,Long> slice) { + super.setDefaultSlice(slice != null ? slice : defaultSlice); + } + } +} diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java index 6a7eef4a17..29a51992ba 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DefaultEvaluator.java @@ -575,10 +575,11 @@ class DefaultEvaluator implements GridCoverage.Evaluator { /* * Above block tried to compute a "CRS to grid" transform in the most direct way. * It covers the usual case where the point has the required number of dimensions, - * and works better if the point has more dimensions (extra dimensions are ignored). + * and fixes the case when the point has more dimensions (extra dimensions are ignored). * The following block covers the opposite case, where the point does not have enough * dimensions. We try to fill missing dimensions with the help of the `slice` map. */ + @SuppressWarnings("LocalVariableHidesMemberVariable") final Map<Integer,Long> slice = getDefaultSlice(); try { CoordinateOperation op = CRS.findOperation(stepCRS, crs, areaOfInterest); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java index dc4a52512a..43261c24d6 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/DerivedGridCoverage.java @@ -93,7 +93,7 @@ abstract class DerivedGridCoverage extends GridCoverage { * * <h4>Differences with usual behavior</h4> * The evaluator returned by the default implementation has two methods with a behavior different - * than the intuitively expected ones. Howerver those differences are allowed by methods contract. + * than the intuitively expected ones. However, those differences are allowed by methods contract. * * <ul> * <li>{@link GridCoverage.Evaluator#getCoverage()} returns an instance which is not {@code this}.</li> diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java index c224df7107..29671ec2fe 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage.java @@ -329,7 +329,7 @@ public abstract class GridCoverage extends BandedCoverage { * <h4>Multi-threading</h4> * {@code Evaluator}s are not thread-safe. For computing sample values concurrently, * a new {@code Evaluator} instance should be created for each thread by invoking this - * method multiply times. + * method multiple times. * * @return a new function for computing or interpolating sample values. * @@ -382,7 +382,7 @@ public abstract class GridCoverage extends BandedCoverage { * Keys are dimensions from 0 inclusive to {@link GridGeometry#getDimension()} exclusive, * and values are the grid coordinates of the slice in the dimension specified by the key. * - * <p>This information allows to invoke {@link #apply(DirectPosition)} with for example + * <p>This information allows to invoke {@link #apply(DirectPosition)} with, for example, * two-dimensional points even if the underlying coverage is three-dimensional. * The missing coordinate values are replaced by the values provided in the map.</p> * diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java index 86dd521f02..38d60c852f 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverage2D.java @@ -647,7 +647,7 @@ public class GridCoverage2D extends GridCoverage { * may force data loading earlier than desired. */ final var result = new ReshapedImage(data, xmin, ymin, xmax, ymax); - return result.isIdentity() ? data : result; + return result.isIdentity() ? result.source : result; } catch (ArithmeticException e) { throw new CannotEvaluateException(e.getMessage(), e); } diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java index 9d8a33a514..47c44ab22d 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/GridCoverageProcessor.java @@ -497,6 +497,34 @@ public class GridCoverageProcessor implements Cloneable { return TranslatedGridCoverage.create(source, null, translation, allowSourceReplacement); } + /** + * Returns the intersection of the given coverage with the given extent. + * The extent shall have the same number of dimensions than the coverage. + * The "grid to <abbr>CRS</abbr>" transform is unchanged. + * + * <p>This method is useful for taking a slice of a multi-dimensional grid. + * Having a slice allows to invoke {@link GridCoverage#render(GridExtent)} + * with a null argument value.</p> + * + * @param source the grid coverage to clip. + * @param clip the clip to apply in units of source grid coordinates. + * @return a coverage with grid coordinates contained inside the given clip. + * @throws IncompleteGridGeometryException if the given coverage has no grid extent. + * @throws DisjointExtentException if the given extent does not intersect the given coverage. + * @throws IllegalArgumentException if axes of the given extent are inconsistent with the axes of the grid of the given coverage. + * + * @since 1.5 + */ + public GridCoverage clip(final GridCoverage source, final GridExtent clip) { + ArgumentChecks.ensureNonNull("source", source); + ArgumentChecks.ensureNonNull("clip", clip); + final boolean allowSourceReplacement; + synchronized (this) { + allowSourceReplacement = optimizations.contains(Optimization.REPLACE_SOURCE); + } + return ClippedGridCoverage.create(source, clip, allowSourceReplacement); + } + /** * Creates a new coverage with a different grid extent, resolution or coordinate reference system. * The desired properties are specified by the {@link GridGeometry} argument, which may be incomplete. @@ -967,10 +995,12 @@ public class GridCoverageProcessor implements Cloneable { @Override public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { - final GridCoverageProcessor other = (GridCoverageProcessor) object; + final var other = (GridCoverageProcessor) object; if (imageProcessor.equals(other.imageProcessor)) { + @SuppressWarnings("LocalVariableHidesMemberVariable") final EnumSet<?> optimizations; synchronized (this) { + // Clone for allowing comparison outside the synchronized block. optimizations = (EnumSet<?>) this.optimizations.clone(); } synchronized (other) { @@ -999,7 +1029,7 @@ public class GridCoverageProcessor implements Cloneable { @Override public GridCoverageProcessor clone() { try { - final GridCoverageProcessor clone = (GridCoverageProcessor) super.clone(); + final var clone = (GridCoverageProcessor) super.clone(); final Field f = GridCoverageProcessor.class.getDeclaredField("imageProcessor"); f.setAccessible(true); // Caller sensitive: must be invoked in same module. f.set(clone, imageProcessor.clone()); diff --git a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java index 26225bd368..8f70ffa212 100644 --- a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java +++ b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/grid/TranslatedGridCoverage.java @@ -18,9 +18,6 @@ package org.apache.sis.coverage.grid; import java.awt.image.RenderedImage; -// Specific to the geoapi-3.1 and geoapi-4.0 branches: -import org.opengis.coverage.CannotEvaluateException; - /** * A grid coverage with the same data as the source coverage, @@ -68,7 +65,7 @@ final class TranslatedGridCoverage extends DerivedGridCoverage { { if (allowSourceReplacement) { while (source instanceof TranslatedGridCoverage) { - final TranslatedGridCoverage tc = (TranslatedGridCoverage) source; + final var tc = (TranslatedGridCoverage) source; final long[] shifted = tc.translation.clone(); long tm = 0; for (int i = Math.min(shifted.length, translation.length); --i >= 0;) { @@ -83,7 +80,7 @@ final class TranslatedGridCoverage extends DerivedGridCoverage { final GridGeometry gridGeometry = source.getGridGeometry(); if (domain == null) { domain = gridGeometry.shiftGrid(translation); - } else if (!domain.extent.isSameSize(gridGeometry.extent)) { + } else if (!domain.getExtent().isSameSize(gridGeometry.extent)) { return null; } if (domain.equals(gridGeometry)) { @@ -110,7 +107,7 @@ final class TranslatedGridCoverage extends DerivedGridCoverage { * the rendered image shall not be translated. */ @Override - public RenderedImage render(GridExtent sliceExtent) throws CannotEvaluateException { + public RenderedImage render(GridExtent sliceExtent) { if (sliceExtent == null) { sliceExtent = gridGeometry.extent; } diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java new file mode 100644 index 0000000000..cc80efe1dd --- /dev/null +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/ClippedGridCoverageTest.java @@ -0,0 +1,202 @@ +/* + * 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.coverage.grid; + +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.awt.image.RenderedImage; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import org.apache.sis.referencing.operation.transform.MathTransforms; +import org.apache.sis.image.privy.RasterFactory; +import org.apache.sis.geometry.DirectPosition2D; + +// 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 ClippedGridCoverage}. + * + * @author Martin Desruisseaux (Geomatys) + */ +public final class ClippedGridCoverageTest extends TestCase { + /** + * Size of the test image, in pixels. + */ + private static final int WIDTH = 7, HEIGHT = 9; + + /** + * Origin of the grid extent used in the test. + */ + private static final int OX = 2, OY = 5; + + /** + * Creates a new test case. + */ + public ClippedGridCoverageTest() { + } + + /** + * Creates a test coverage. + * + * @param processor non-null for hiding the {@link BufferedImage} implementation class. + */ + private static GridCoverage createCoverage(final GridCoverageProcessor processor) { + final GridExtent extent = new GridExtent(WIDTH, HEIGHT).translate(OX, OY); + final GridGeometry domain = new GridGeometry(extent, PixelInCell.CELL_CORNER, MathTransforms.scale(4, 2), HardCodedCRS.WGS84); + final BufferedImage data = RasterFactory.createGrayScaleImage(DataBuffer.TYPE_BYTE, WIDTH, HEIGHT, 1, 0, 0, 100); + final WritableRaster raster = data.getRaster(); + for (int y=0; y<HEIGHT; y++) { + for (int x=0; x<WIDTH; x++) { + raster.setSample(x, y, 0, 10*y+x); + } + } + RenderedImage image = data; + if (processor != null) { + // We are not really interrested in statistics, we just want to get a different implementation class. + image = processor.imageProcessor.statistics(image, null, null); + } + return new GridCoverageBuilder().setDomain(domain).setValues(image).build(); + } + + /** + * Verifies that the given two-dimensional extent has the given low coordinates and size. + */ + private static void assertExtentStarts(final GridExtent extent, final long low0, final long low1, final long size0, final long size1) { + assertEquals(2, extent.getDimension()); + assertEquals(low0, extent.getLow (0)); + assertEquals(low1, extent.getLow (1)); + assertEquals(size0, extent.getSize(0)); + assertEquals(size1, extent.getSize(1)); + } + + /** + * Asserts that the value of the first pixel of the given image is equal to the given value. + */ + private static void assertFirstPixelEquals(final int expected, final RenderedImage image) { + final Raster tile = image.getTile(0, 0); + assertEquals(expected, tile.getSample(0, 0, 0)); + } + + /** + * Tests the clipping of a coverage backed by a {@link BufferedImage}. + */ + @Test + public void testWithBufferedImage() { + test(false); + } + + /** + * Tests the clipping of a coverage backed by something else than {@link BufferedImage}. + */ + @Test + public void testWithRenderedImage() { + test(true); + } + + /** + * Shared implementation of {@link #testWithBufferedImage()} and {@link #testWithRenderedImage()}. + * + * @param hide whether to hide the {@link BufferedImage} implementation. + */ + private static void test(final boolean hide) { + final var clip = new GridExtent(WIDTH - 3, HEIGHT - 2).translate(OX - 1, OY + 1); + final var processor = new GridCoverageProcessor(); + final GridCoverage source = createCoverage(hide ? processor : null); + final GridCoverage target = processor.clip(source, clip); + assertExtentStarts(source.getGridGeometry().getExtent(), OX, OY, WIDTH, HEIGHT); + assertExtentStarts(target.getGridGeometry().getExtent(), OX, OY+1, WIDTH-4, HEIGHT-2); + /* + * Verifications on the source as a matter of principle. + * This is for making sure that the verifications on the target are okay. + */ + RenderedImage image = source.render(null); + assertEquals(WIDTH, image.getWidth()); + assertEquals(HEIGHT, image.getHeight()); + assertEquals(0, image.getMinX()); // The returned image match exactly the request. + assertEquals(0, image.getMinY()); + assertFirstPixelEquals(0, image); + /* + * Verification on the target. In the general case, the image is translated instead of clipped + * for having at the (0,0) coordinates the pixel that we would have if the image was clipped. + * This is because `GridCoverage2D` does not know how to clip an arbitrary rendered image. + * Only in the particular case of `BufferedImage`, a real clip is expected. + */ + image = target.render(null); + if (hide) { + assertEquals(WIDTH, image.getWidth()); + assertEquals(HEIGHT, image.getHeight()); + assertEquals( 0, image.getMinX()); + assertEquals(-1, image.getMinY()); + } else { + assertEquals(WIDTH - 4, image.getWidth()); + assertEquals(HEIGHT - 2, image.getHeight()); + assertEquals(0, image.getMinX()); + assertEquals(0, image.getMinY()); + } + assertFirstPixelEquals(10, image); + /* + * The result for identical "real world" coordinates should be the same for both coverages. + */ + final var p = new DirectPosition2D(HardCodedCRS.WGS84, 15, 20); + assertArrayEquals(source.evaluator().apply(p), + target.evaluator().apply(p)); + /* + * Test again with a sub-region having its origin inside the clipped area. Because of that, + * `ClippedGridCoverage` does not apply any additional translation on the rendered image. + */ + image = target.render(new GridExtent(WIDTH, HEIGHT).translate(OX+1, OY+2)); + if (hide) { + assertEquals(WIDTH, image.getWidth()); + assertEquals(HEIGHT, image.getHeight()); + assertEquals(-1, image.getMinX()); + assertEquals(-2, image.getMinY()); + } else { + assertEquals(WIDTH - 5, image.getWidth()); + assertEquals(HEIGHT - 3, image.getHeight()); + assertEquals(0, image.getMinX()); + assertEquals(0, image.getMinY()); + } + assertFirstPixelEquals(21, image); + assertArrayEquals(source.evaluator().apply(p), + target.evaluator().apply(p)); + /* + * Test again with a sub-region having its origin outside the clipped area. + * `ClippedGridCoverage` must add a translation for the difference between + * the request and what we got. + */ + image = target.render(new GridExtent(WIDTH, HEIGHT).translate(OX+1, OY-1)); + if (hide) { + assertEquals(WIDTH, image.getWidth()); + assertEquals(HEIGHT, image.getHeight()); + assertEquals(-1, image.getMinX()); + assertEquals(+1, image.getMinY()); + } else { + assertEquals(WIDTH - 5, image.getWidth()); + assertEquals(HEIGHT - 2, image.getHeight()); + assertEquals(0, image.getMinX()); + assertEquals(2, image.getMinY()); + } + // Do not test pixel at (0,0) because it does not exist. + assertArrayEquals(source.evaluator().apply(p), + target.evaluator().apply(p)); + } +} diff --git a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java index ce5645ebca..37aae6af4f 100644 --- a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java +++ b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/coverage/grid/TranslatedGridCoverageTest.java @@ -73,7 +73,7 @@ public final class TranslatedGridCoverageTest extends TestCase { */ @Test public void testUsingProcessor() { - final GridCoverageProcessor processor = new GridCoverageProcessor(); + final var processor = new GridCoverageProcessor(); final GridCoverage source = createCoverage(); final GridCoverage target = processor.shiftGrid(source, 30, -5); assertExtentStarts(source.getGridGeometry().getExtent(), -20, -10); @@ -81,7 +81,7 @@ public final class TranslatedGridCoverageTest extends TestCase { /* * The result for identical "real world" coordinates should be the same for both coverages. */ - final DirectPosition2D p = new DirectPosition2D(HardCodedCRS.WGS84, -75, -18); + final var p = new DirectPosition2D(HardCodedCRS.WGS84, -75, -18); assertArrayEquals(source.evaluator().apply(p), target.evaluator().apply(p)); } diff --git a/netbeans-project/nbproject/project.xml b/netbeans-project/nbproject/project.xml index 8ae7f076ad..f578f8c466 100644 --- a/netbeans-project/nbproject/project.xml +++ b/netbeans-project/nbproject/project.xml @@ -32,6 +32,7 @@ <word>denormalization</word> <word>genericity</word> <word>Geopackage</word> + <word>geospatial</word> <word>Molodensky</word> <word>transformative</word> <word>untiled</word>
