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.

Reply via email to