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 77cdb0047c6203bdf01102109ee055c18046b60e Author: Martin Desruisseaux <martin.desruisse...@geomatys.com> AuthorDate: Fri May 26 12:13:03 2023 +0200 When the GeoTIFF image is of floating point type, replace fill values by NaN at reading time. This is the same policy than with our netCDF reader. --- .../org/apache/sis/coverage/SampleDimension.java | 7 +++++ .../sis/internal/coverage/SampleDimensions.java | 6 ++-- .../java/org/apache/sis/measure/NumberRange.java | 4 +-- .../sis/internal/geotiff/SchemaModifier.java | 2 +- .../sis/storage/geotiff/CompressedSubset.java | 4 +-- .../org/apache/sis/storage/geotiff/DataCube.java | 18 ++++++++++++ .../org/apache/sis/storage/geotiff/DataSubset.java | 33 +++++++++++++++++++++- .../sis/storage/geotiff/ImageFileDirectory.java | 13 +++++++-- 8 files changed, 77 insertions(+), 10 deletions(-) diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java index f0a24e8c9e..4ec24af099 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java +++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleDimension.java @@ -1209,6 +1209,13 @@ public class SampleDimension implements Serializable { toNaN.remove(c); return c; } + + /** Removes all categories in the given range. */ + @Override protected void removeRange(final int fromIndex, final int toIndex) { + System.arraycopy(categories, toIndex, categories, fromIndex, count - toIndex); + Arrays.fill(categories, toIndex, count, null); + count -= (toIndex - fromIndex); + } }; } diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java index 9952fb69c8..c7658d01a3 100644 --- a/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java +++ b/core/sis-feature/src/main/java/org/apache/sis/internal/coverage/SampleDimensions.java @@ -155,8 +155,9 @@ public final class SampleDimensions extends Static { final SampleDimension band = bands[i]; if (band != null) { final List<Category> categories = band.getCategories(); - final Number[] nodataValues = new Number[categories.size()]; - for (int j = 0; j < nodataValues.length; j++) { + final int count = categories.size(); + final Number[] nodataValues = new Number[count + 1]; + for (int j = 0; j < count; j++) { final Category category = categories.get(j); if (!category.isQuantitative()) { final NumberRange<?> range = category.getSampleRange(); @@ -171,6 +172,7 @@ public final class SampleDimensions extends Static { nodataValues[j] = value; } } + nodataValues[count] = band.getBackground().orElse(null); sampleFilters[i] = ImageProcessor.filterNodataValues(nodataValues); } } diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java index 019eda568d..c4cc0a29a3 100644 --- a/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java +++ b/core/sis-utility/src/main/java/org/apache/sis/measure/NumberRange.java @@ -729,8 +729,8 @@ public class NumberRange<E extends Number & Comparable<? super E>> extends Range * This method converts {@code this} or the given argument to the widest numeric type, * then performs the same work than {@link #contains(Comparable)}. * - * @param value the value to check for inclusion in this range. - * @return {@code true} if the given value is included in this range. + * @param value the value to check for inclusion in this range, or {@code null}. + * @return {@code true} if the given value is non-null and included in this range. * @throws IllegalArgumentException if the given range cannot be converted to a valid type * through widening conversion. */ diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java index 40d751d177..0f0659c2a6 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/geotiff/SchemaModifier.java @@ -112,7 +112,7 @@ public interface SchemaModifier { * @todo if we move this key in public API in the future, then it would be a * value in existing {@link org.apache.sis.storage.DataOptionKey} class. */ - OptionKey<SchemaModifier> OPTION = new InternalOptionKey<SchemaModifier>("SCHEMA_MODIFIER", SchemaModifier.class); + OptionKey<SchemaModifier> OPTION = new InternalOptionKey<>("SCHEMA_MODIFIER", SchemaModifier.class); /** * The default instance which performs no modification. diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java index 2639a4209f..fe926da63a 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/CompressedSubset.java @@ -35,7 +35,7 @@ import static java.lang.Math.multiplyFull; * Raster data obtained from a compressed GeoTIFF file in the domain requested by user. * * @author Martin Desruisseaux (Geomatys) - * @version 1.1 + * @version 1.4 * @since 1.1 */ final class CompressedSubset extends DataSubset { @@ -249,7 +249,7 @@ final class CompressedSubset extends DataSubset { fillRemainingRows(bank.flip()); banks[b] = bank; } - return Raster.createWritableRaster(model, RasterFactory.wrap(dataType, banks), location); + return createWritableRaster(RasterFactory.wrap(dataType, banks), location); } /** diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java index fc2a2679f4..e8b09a1f50 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataCube.java @@ -160,6 +160,24 @@ abstract class DataCube extends TiledGridResource implements ResourceOnFileSyste */ abstract Predictor getPredictor(); + /** + * Returns the fill value used in image of floating point type before replacement by NaN. + * If no value is declared or if the image type is an integer type, returns {@code null}. + * If non-null, this value will be replaced by {@link Float#NaN} at reading time. + * + * <div class="note"><b>Rational:</b> + * the intend is to handle the image as if it was already converted to the units of measurement. + * Our netCDF reader does the same thing, and we want a consistent behavior of coverage readers. + * </div> + * + * If this method returns a non-null value, then {@link #getFillValue()} should return NaN. + * + * @return value to be replaced by NaN at reading time, or {@code null} if none. + * + * @see #getFillValue() + */ + abstract Number getReplaceableFillValue(); + /** * Returns {@code true} if the image can be read with the {@link DataSubset} base class, * or {@code false} if the more sophisticated {@link CompressedSubset} sub-class is needed. diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java index 043a3df5d4..58192bc408 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DataSubset.java @@ -24,6 +24,8 @@ import java.io.IOException; import java.awt.Point; import java.awt.image.BandedSampleModel; import java.awt.image.DataBuffer; +import java.awt.image.DataBufferFloat; +import java.awt.image.DataBufferDouble; import java.awt.image.Raster; import org.opengis.util.GenericName; import org.apache.sis.image.DataType; @@ -41,6 +43,7 @@ import org.apache.sis.internal.storage.io.ChannelDataInput; import org.apache.sis.internal.geotiff.Resources; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.Localized; +import org.apache.sis.util.ArraysExt; import org.apache.sis.math.Vector; import static java.lang.Math.addExact; @@ -523,7 +526,7 @@ class DataSubset extends TiledGridCoverage implements Localized { banks[b] = bank; } final DataBuffer buffer = RasterFactory.wrap(type, banks); - return Raster.createWritableRaster(model, buffer, location); + return createWritableRaster(buffer, location); } /** @@ -544,4 +547,32 @@ class DataSubset extends TiledGridCoverage implements Localized { } } } + + /** + * Creates the raster with the given data buffer, which contains the pixels that have been read. + * + * @param buffer the sample values which have been read from the GeoTIFF tile. + * @param location pixel coordinates in the upper-left corner of the tile to return. + * @return raster with the given sample values. + */ + final Raster createWritableRaster(final DataBuffer buffer, final Point location) { + final Number fill = source.getReplaceableFillValue(); + if (fill != null) { + switch (buffer.getDataType()) { + case DataBuffer.TYPE_FLOAT: { + for (float[] bank : ((DataBufferFloat) buffer).getBankData()) { + ArraysExt.replace(bank, fill.floatValue(), Float.NaN); + } + break; + } + case DataBuffer.TYPE_DOUBLE: { + for (double[] bank : ((DataBufferDouble) buffer).getBankData()) { + ArraysExt.replace(bank, fill.doubleValue(), Double.NaN); + } + break; + } + } + } + return Raster.createWritableRaster(model, buffer, location); + } } diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java index be107010df..0d255f0953 100644 --- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java +++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java @@ -1691,6 +1691,15 @@ final class ImageFileDirectory extends DataCube { return colorModel; } + /** + * Returns the fill value to be replaced by NaN at reading time. + * This is possible only for images of floating point type. + */ + @Override + Number getReplaceableFillValue() { + return (sampleFormat == FLOAT) ? noData : null; + } + /** * Returns the value to use for filling empty spaces in the raster, or {@code null} if none, * not different than zero or not valid for the target data type. @@ -1698,11 +1707,11 @@ final class ImageFileDirectory extends DataCube { */ @Override protected Number getFillValue() { - return getFillValue(false); + return (sampleFormat != FLOAT) ? getFillValue(false) : Double.NaN; } /** - * Returns the value to use for filling empty spaces in the raster, or {@code null} if none, + * Returns the value to use for filling empty spaces in the raster, or {@code null} if none. * The exclusion of zero value is optional, controlled by the {@code acceptZero} argument. * * @param acceptZero whether to return a number for the zero value.