-
Notifications
You must be signed in to change notification settings - Fork 51
Fix an instability in Geotiff unit test #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: geoapi-4.0
Are you sure you want to change the base?
Changes from all commits
7853870
2843f7b
76febad
81ff7e1
ed5617b
ad33ef9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,9 @@ | |
| import java.util.Optional; | ||
| import java.io.Serializable; | ||
| import javax.measure.Unit; | ||
| import org.apache.sis.util.ComparisonMode; | ||
| import org.apache.sis.util.LenientComparable; | ||
| import org.apache.sis.util.Utilities; | ||
| import org.opengis.util.GenericName; | ||
| import org.opengis.util.InternationalString; | ||
| import org.opengis.referencing.operation.MathTransform1D; | ||
|
|
@@ -85,7 +88,7 @@ | |
| * | ||
| * @since 1.0 | ||
| */ | ||
| public class SampleDimension implements IdentifiedType, Serializable { | ||
| public class SampleDimension implements IdentifiedType, LenientComparable, Serializable { | ||
| /** | ||
| * Serial number for inter-operability with different versions. | ||
| */ | ||
|
|
@@ -498,6 +501,18 @@ public boolean equals(final Object object) { | |
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object other, ComparisonMode mode) { | ||
| if (other == this) return true; | ||
| if (mode.isApproximate() && other instanceof SampleDimension) { | ||
| final var otherDim = (SampleDimension) other; | ||
| return Utilities.deepEquals(this.transferFunction, otherDim.transferFunction, mode) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The transfer function was not compared in |
||
| && Utilities.deepEquals(this.categories, otherDim.categories, mode) | ||
| && Utilities.deepEquals(this.background, otherDim.background, mode); | ||
| } | ||
| return equals(other); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a string representation of this sample dimension. | ||
| * This string is for debugging purpose only and may change in future version. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,15 +16,16 @@ | |
| */ | ||
| package org.apache.sis.storage.geotiff; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.io.ByteArrayOutputStream; | ||
| import java.nio.ByteBuffer; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Files; | ||
| import java.awt.Dimension; | ||
| import java.awt.Rectangle; | ||
| import java.awt.image.BufferedImage; | ||
| import java.awt.image.RenderedImage; | ||
| import org.apache.sis.storage.StorageConnector; | ||
| import org.apache.sis.util.Utilities; | ||
| import org.opengis.referencing.cs.AxisDirection; | ||
| import org.opengis.referencing.operation.TransformException; | ||
| import org.apache.sis.geometry.Envelopes; | ||
|
|
@@ -44,10 +45,13 @@ | |
| import org.apache.sis.referencing.operation.matrix.Matrix4; | ||
|
|
||
| // Test dependencies | ||
| import org.junit.jupiter.api.Disabled; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.*; | ||
| import static org.apache.sis.test.Assertions.assertSingleton; | ||
| import static org.apache.sis.feature.Assertions.assertGridToCornerEquals; | ||
| import static org.apache.sis.feature.Assertions.assertPixelsEqual; | ||
| import org.apache.sis.test.TestCase; | ||
| import org.apache.sis.referencing.crs.HardCodedCRS; | ||
| import org.apache.sis.referencing.operation.HardCodedConversions; | ||
|
|
@@ -138,7 +142,7 @@ public void testNonLinearVerticalTransform() throws Exception { | |
| */ | ||
| @Test | ||
| public void testWriteUntiled() throws Exception { | ||
| testWrite(UNTILED, new Rectangle(32, 16), null, 1054); | ||
| testWrite(new Rectangle(32, 16), null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -149,19 +153,31 @@ public void testWriteUntiled() throws Exception { | |
| @Test | ||
| public void testWriteTiled() throws Exception { | ||
| final var tileSize = new Dimension(16, 16); // TIFF tile size must be multiple of 16. | ||
| testWrite(TILED, new Rectangle(tileSize.width * 3, tileSize.height * 2), tileSize, 2334); | ||
| testWrite(new Rectangle(tileSize.width * 3, tileSize.height * 2), tileSize); | ||
| } | ||
|
|
||
| /** | ||
| * Writes an image and compare with the {@code "tiled.tiff"} file. | ||
| * This test differs from {@link #testWriteTiled()} because it requests a tile size that is not accepted as is by | ||
| * geotiff. | ||
| * </br> | ||
| * The aim of this test is to ensure that Geotiff writer will adapt tile size according to the Tiff standard. | ||
| * It requests tiles of size 19, and expect the Geotiff writer to adapt request to write tiles of size 16 or 32. | ||
| */ | ||
| @Test | ||
| public void testWriteTiledAdapted() throws Exception { | ||
| final var tileSize = new Dimension(7, 7); | ||
| testWrite(new Rectangle(64, 64), tileSize); | ||
| } | ||
|
|
||
| /** | ||
| * Implementation of {@link #testWriteUntiled()} and {@link #testWriteTiled()}. | ||
| * | ||
| * @param filename name of the file which contain the expected image. | ||
| * @param bounds bounds of the image to create. | ||
| * @param tileSize size of the tiles, or {@code null} for the image size. | ||
| * @param length expected length in bytes. | ||
| */ | ||
| private static void testWrite(final String filename, final Rectangle bounds, final Dimension tileSize, final int length) | ||
| throws TransformException, DataStoreException, IOException | ||
| private static void testWrite(final Rectangle bounds, final Dimension tileSize) | ||
| throws TransformException, DataStoreException | ||
| { | ||
| /* | ||
| * We need a CRS which has no EPSG code for ensuring that the test write the same GeoTIFF keys | ||
|
|
@@ -177,17 +193,99 @@ private static void testWrite(final String filename, final Rectangle bounds, fin | |
| .flipGridAxis(1) | ||
| .build(); | ||
|
|
||
| final var buffer = new ByteArrayOutputStream(length); | ||
| final var buffer = new ByteArrayOutputStream(); | ||
| try (DataStore ds = DataStores.openWritable(buffer, "geotiff")) { | ||
| assertInstanceOf(GeoTiffStore.class, ds).append(coverage, null); | ||
| } | ||
|
|
||
| final byte[] actual = buffer.toByteArray(); | ||
| final byte[] expected; | ||
| try (InputStream in = GeoTiffStoreTest.class.getResourceAsStream(filename)) { | ||
| assertNotNull(in, filename); | ||
| expected = in.readAllBytes(); | ||
| try (var store = new GeoTiffStore(new GeoTiffStoreProvider(), new StorageConnector(ByteBuffer.wrap(actual)))) { | ||
| var coverageToValidate = store.components().get(0).read(null); | ||
| final var expectedGridGeom = coverage.getGridGeometry(); | ||
| final var actualGridGeom = coverageToValidate.getGridGeometry(); | ||
| assertTrue( | ||
| Utilities.equalsApproximately(expectedGridGeom, actualGridGeom), | ||
| () -> String.format( | ||
| "Written grid geometry differs from original one.%nOriginal:%n%s%nWritten:%n%s%n", | ||
| expectedGridGeom, actualGridGeom | ||
| ) | ||
| ); | ||
|
|
||
| assertTrue( | ||
| Utilities.equalsApproximately(expectedGridGeom, actualGridGeom), | ||
| () -> String.format( | ||
| "Written grid geometry differs from original one.%nOriginal:%n%s%nWritten:%n%s%n", | ||
| expectedGridGeom, actualGridGeom | ||
| ) | ||
| ); | ||
|
|
||
| final var expectedSampleDims = coverage.getSampleDimensions(); | ||
| final var actualSampleDims = coverageToValidate.getSampleDimensions(); | ||
| assertTrue( | ||
| Utilities.equalsApproximately(expectedSampleDims, actualSampleDims), | ||
| () -> String.format( | ||
| "Written Sample dimensions differ from original one.%nOriginal:%n%s%nWritten:%n%s%n", | ||
| expectedSampleDims, actualSampleDims | ||
| ) | ||
| ); | ||
|
|
||
| final var actualRendering = coverageToValidate.render(null); | ||
| assertPixelsEqual(coverage.render(null), null, actualRendering, null); | ||
| // If user requested a tiled dataset, we must ensure the written Geotiff file has been tiled | ||
| if (tileSize != null && (tileSize.getWidth() < bounds.getWidth() || tileSize.getHeight() < bounds.getHeight())) { | ||
| assertTiling(actualRendering, tileSize, 16); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Represent the side of the tile being evaluated. Either width (X) or height (Y). | ||
| */ | ||
| private enum TileAxis { width, height } | ||
|
|
||
| /** | ||
| * Verify that given image tiling respects user tiling request, modulo a given restriction. | ||
| * The restriction maps Tiff standard requirement for tile size to be multiple of a given factor. | ||
| * </br> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is a small detail, but the rest of SIS rather uses |
||
| * It means that if user requests a tile size of 3, but the restriction factor is 2, | ||
| * then we expect the image to use a tile size of either 2 or 4, | ||
| * which are the nearest enclosing multiples of 2 for request 3. | ||
| * | ||
| * @param actualRendering The image to control tiling on. | ||
| * @param tileSize The tile size requested by user. | ||
| * @param tileSizeMultiple A factor to use to adapted requested tile size. | ||
| */ | ||
| private static void assertTiling(RenderedImage actualRendering, Dimension tileSize, int tileSizeMultiple) { | ||
| assertTileSize(TileAxis.width, actualRendering.getWidth(), actualRendering.getTileWidth(), tileSize.width, tileSizeMultiple); | ||
| assertTileSize(TileAxis.height, actualRendering.getHeight(), actualRendering.getTileHeight(), tileSize.height, tileSizeMultiple); | ||
| } | ||
|
|
||
| /** | ||
| * Test a specific tile side according to requirements expressed by {@link #assertTiling(RenderedImage, Dimension, int)}. | ||
| * | ||
| * @param axis Which side of the tiling is being tested. Used for assertion error message formatting. | ||
| * @param imgSize The image actual size along tested side (its {@link RenderedImage#getWidth() width} or {@link RenderedImage#getHeight() height}). | ||
| * @param imgActualTileSize The image actual tile size along tested side (its {@link RenderedImage#getTileWidth() tile width} or {@link RenderedImage#getTileHeight() tile height}). | ||
| * @param requestedTileSize User request tile size along the side to test. | ||
| * @param tileSizeMultiple The restriction factor: actual tile size must be a multiple of this value, independently of the user request. | ||
| */ | ||
| private static void assertTileSize(TileAxis axis, int imgSize, int imgActualTileSize, int requestedTileSize, int tileSizeMultiple) { | ||
| if (imgSize > requestedTileSize) { | ||
| final int modulo = requestedTileSize % tileSizeMultiple; | ||
| if (modulo == 0) { | ||
| assertEquals(requestedTileSize, imgActualTileSize, () -> "Tile " + axis); | ||
| } else if (requestedTileSize < tileSizeMultiple) { | ||
| assertEquals(tileSizeMultiple, imgActualTileSize, () -> "Tile " + axis); | ||
| } else { | ||
| final var minTileSize = requestedTileSize - modulo; | ||
| final var maxTileSize = requestedTileSize + (tileSizeMultiple - modulo); | ||
| assertTrue(imgActualTileSize == minTileSize || imgActualTileSize == maxTileSize, | ||
| () -> String.format( | ||
| "Tile %s should be either %d or %d (because it must be a multiple of %d), but it is %d", | ||
| axis, minTileSize, maxTileSize, tileSizeMultiple, imgActualTileSize | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| assertArrayEquals(expected, actual); | ||
| assertEquals(length, actual.length); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only test if the mode is
STRICT, in which case the test should be done as in the current implementation ofequals(Object), and unconditionally usesUtilities.deepEqualsin all other cases. The reason is that since we need to makeCategoryimplementsLenientComparabletoo,Categorycould apply whether policy it wants. In particular, it may compare the category name inComparisonMode.BY_CONTRACTand ignore that name inComparisonMode.IGNORE_METADATA.