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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b26d88a663 Store information about the "no data" value used in an 
ASCII Grid file. It will be needed for re-exporting data in ASCII Grid again.
b26d88a663 is described below

commit b26d88a66305ee513e3d11bf55ad66777b3e3d90
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Wed Apr 6 01:27:15 2022 +0200

    Store information about the "no data" value used in an ASCII Grid file.
    It will be needed for re-exporting data in ASCII Grid again.
---
 .../org/apache/sis/coverage/SampleDimension.java   | 34 +++++++++++++++---
 .../apache/sis/internal/storage/ascii/Store.java   | 40 +++++++++++++++-------
 .../sis/internal/storage/ascii/StoreTest.java      | 22 +++++++++++-
 3 files changed, 78 insertions(+), 18 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 a8063f678e..9aad5130a0 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
@@ -549,6 +549,32 @@ public class SampleDimension implements Serializable {
      * @module
      */
     public static class Builder {
+        /**
+         * The default name used for quantitative categories.
+         *
+         * @see #addQuantitative(CharSequence, NumberRange, MathTransform1D, 
Unit)
+         */
+        private static final InternationalString DATA = 
Vocabulary.formatInternational(Vocabulary.Keys.Data);
+
+        /**
+         * The default name used for qualitative categories.
+         *
+         * @see #addQualitative(CharSequence, NumberRange)
+         */
+        private static final InternationalString NODATA = 
Vocabulary.formatInternational(Vocabulary.Keys.Nodata);
+
+        /**
+         * The default name used for background.
+         * The difference between "no data" and "fill value" is that "no data" 
is used when a value
+         * is inside the coverage domain of validity but missing, for example 
because of clouds.
+         * By contrast the fill value is used for values outside the coverage 
domain of validity
+         * when the empty space must be filled with something. It happens for 
example when the
+         * coverage is rotated inside the rectangular bounds of the rendered 
image.
+         *
+         * @see #setBackground(CharSequence, Number)
+         */
+        private static final InternationalString FILL_VALUE = 
Vocabulary.formatInternational(Vocabulary.Keys.FillValue);
+
         /**
          * Identification for this sample dimension.
          */
@@ -741,7 +767,7 @@ public class SampleDimension implements Serializable {
         public Builder setBackground(CharSequence name, Number sample) {
             ArgumentChecks.ensureNonNull("sample", sample);
             if (name == null) {
-                name = 
Vocabulary.formatInternational(Vocabulary.Keys.FillValue);
+                name = FILL_VALUE;
             }
             final NumberRange<?> samples = range(sample.getClass(), sample, 
sample);
             // Use of `getMinValue()` below shall be consistent with 
ToNaN.remove(Category).
@@ -931,7 +957,7 @@ public class SampleDimension implements Serializable {
          */
         public Builder addQualitative(CharSequence name, final NumberRange<?> 
samples) {
             if (name == null) {
-                name = Vocabulary.formatInternational(Vocabulary.Keys.Nodata);
+                name = NODATA;
             }
             add(new Category(name, samples, null, null, toNaN));
             return this;
@@ -992,7 +1018,7 @@ public class SampleDimension implements Serializable {
                 throw new 
IllegalArgumentException(Errors.format(Errors.Keys.ValueAlreadyDefined_1, "NaN 
#" + ordinal));
             }
             if (name == null) {
-                name = Vocabulary.formatInternational(Vocabulary.Keys.Nodata);
+                name = NODATA;
             }
             add(new Category(name, samples, null, null, (v) -> ordinal));
             return this;
@@ -1139,7 +1165,7 @@ public class SampleDimension implements Serializable {
         public Builder addQuantitative(CharSequence name, NumberRange<?> 
samples, MathTransform1D toUnits, Unit<?> units) {
             ArgumentChecks.ensureNonNull("toUnits", toUnits);
             if (name == null) {
-                name = Vocabulary.formatInternational(Vocabulary.Keys.Data);
+                name = DATA;
             }
             add(new Category(name, samples, toUnits, units, toNaN));
             return this;
diff --git 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
index d67738f34f..411490d10b 100644
--- 
a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
+++ 
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ascii/Store.java
@@ -99,6 +99,11 @@ final class Store extends PRJDataStore implements 
GridCoverageResource {
         "DX",        "DY"
     };
 
+    /**
+     * The default no-data value. This is part of the ASCII Grid format 
specification.
+     */
+    private static final double DEFAULT_NODATA = -9999;
+
     /**
      * The object to use for reading data, or {@code null} if the channel has 
been closed.
      * Note that a null value does not necessarily means that the store is 
closed, because
@@ -116,13 +121,14 @@ final class Store extends PRJDataStore implements 
GridCoverageResource {
      * The optional {@code NODATA_VALUE} attribute, or {@code NaN} if none.
      * This value is valid only if {@link #gridGeometry} is non-null.
      */
-    private double fillValue;
+    private double nodataValue;
 
     /**
-     * The {@link #fillValue} as a text. This is useful when the fill value
-     * can not be parsed as a {@code double} value, for example {@code "N/A"}.
+     * The {@link #nodataValue} as a text. This is useful when the fill value
+     * can not be parsed as a {@code double} value, for example {@code "NULL"},
+     * {@code "N/A"}, {@code "NA"}, {@code "mv"}, {@code "!"} or {@code "-"}.
      */
-    private String fillText;
+    private String nodataText;
 
     /**
      * The image size together with the "grid to CRS" transform.
@@ -152,7 +158,6 @@ final class Store extends PRJDataStore implements 
GridCoverageResource {
      */
     public Store(final StoreProvider provider, final StorageConnector 
connector) throws DataStoreException {
         super(provider, connector);
-        fillValue = Double.NaN;
         input = new CharactersView(connector.commit(ChannelDataInput.class, 
StoreProvider.NAME), null);
         listeners.useWarningEventsOnly();
     }
@@ -219,11 +224,15 @@ cellsize:       if (value != null) {
                  * This reader accepts a value both as text and as a floating 
point.
                  * The intent is to accept unparsable texts such as "NULL".
                  */
-                fillText = header.remove(key = NODATA_VALUE);
-                if (fillText != null) try {
-                    fillValue = Double.parseDouble(fillText);
+                nodataText = header.remove(key = NODATA_VALUE);
+                if (nodataText != null) try {
+                    nodataValue = Double.parseDouble(nodataText);
                 } catch (NumberFormatException e) {
+                    nodataValue = Double.NaN;
                     
listeners.warning(messageForProperty(Errors.Keys.IllegalValueForProperty_2, 
key), e);
+                } else {
+                    nodataValue = DEFAULT_NODATA;
+                    nodataText  = "null";         // "NaN" is already 
understood by `parseDouble(String)`.
                 }
             } catch (NumberFormatException e) {
                 throw new 
DataStoreContentException(messageForProperty(Errors.Keys.IllegalValueForProperty_2,
 key), e);
@@ -384,11 +393,11 @@ cellsize:       if (value != null) {
                 double value;
                 try {
                     value = Double.parseDouble(token);
-                    if (value == fillValue) {
+                    if (value == nodataValue) {
                         value = Double.NaN;
                     }
                 } catch (NumberFormatException e) {
-                    if (token.equalsIgnoreCase(fillText)) {
+                    if (token.equalsIgnoreCase(nodataText)) {
                         value = Double.NaN;
                     } else {
                         throw new 
DataStoreContentException(Resources.forLocale(getLocale()).getString(
@@ -400,7 +409,8 @@ cellsize:       if (value != null) {
             }
             /*
              * At this point we finished to read the full image. Close the 
channel now and build the sample dimension.
-             * The sample dimension does not contain NODATA_VALUE because we 
already converted them to NaN.
+             * We add a category for the NODATA_VALUE even if this value does 
not appear anymore in the `data` array
+             * (since we replaced it by NaN on-the-fly) because this 
information is needed by `WritableStore`.
              *
              * TODO: a future version could try to convert the image to 
integer values.
              * In this case only we may need to declare the NODATA_VALUE.
@@ -413,8 +423,12 @@ cellsize:       if (value != null) {
                 minimum = 0;
                 maximum = 1;
             }
-            final SampleDimension.Builder b = new 
SampleDimension.Builder().setName(filename);
-            final SampleDimension band = b.addQuantitative(null, minimum, 
maximum, null).build();
+            final SampleDimension.Builder b = new SampleDimension.Builder();
+            b.setName(filename).addQuantitative(null, minimum, maximum, null);
+            if (nodataValue < minimum || nodataValue > maximum) {
+                b.mapQualitative(null, nodataValue, Float.NaN);
+            }
+            final SampleDimension band = b.build().forConvertedValues(true);
             /*
              * Build the coverage last, because a non-null `coverage` field
              * is used for meaning that everything succeed.
diff --git 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
index 4df0a60adc..2f8804f188 100644
--- 
a/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
+++ 
b/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/ascii/StoreTest.java
@@ -16,11 +16,13 @@
  */
 package org.apache.sis.internal.storage.ascii;
 
+import java.util.List;
 import java.awt.image.Raster;
 import java.awt.image.RenderedImage;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import org.opengis.metadata.identification.Identification;
+import org.apache.sis.coverage.Category;
 import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
@@ -48,7 +50,9 @@ public final strictfp class StoreTest extends TestCase {
     }
 
     /**
-     * Tests the metadata of the {@code "grid.asc"} file.
+     * Tests the metadata of the {@code "grid.asc"} file. This test reads only 
the header.
+     * It should not test sample dimensions or pixel values, because doing so 
read the full
+     * image and is the purpose of {@link #testRead()}.
      *
      * @throws DataStoreException if an error occurred while reading the file.
      */
@@ -73,6 +77,10 @@ public final strictfp class StoreTest extends TestCase {
                     
getSingleton(getSingleton(id.getExtents()).getGeographicElements());
             assertEquals(-84, bbox.getSouthBoundLatitude(), 1);
             assertEquals(+85, bbox.getNorthBoundLatitude(), 1);
+            /*
+             * Verify that the metadata is cached.
+             */
+            assertSame(metadata, store.getMetadata());
         }
     }
 
@@ -84,6 +92,14 @@ public final strictfp class StoreTest extends TestCase {
     @Test
     public void testRead() throws DataStoreException {
         try (Store store = open()) {
+            final List<Category> categories = 
getSingleton(store.getSampleDimensions()).getCategories();
+            assertEquals(2, categories.size());
+            assertEquals(   -2, 
categories.get(0).getSampleRange().getMinDouble(), 1);
+            assertEquals(   30, 
categories.get(0).getSampleRange().getMaxDouble(), 1);
+            assertEquals(-9999, 
categories.get(1).forConvertedValues(false).getSampleRange().getMinDouble(), 0);
+            /*
+             * Check sample values.
+             */
             final GridCoverage coverage = store.read(null, null);
             final RenderedImage image = coverage.render(null);
             assertEquals(10, image.getWidth());
@@ -94,6 +110,10 @@ public final strictfp class StoreTest extends TestCase {
             assertEquals(Float.NaN, tile.getSampleFloat(9, 19, 0), 0f);
             assertEquals(  -1.075f, tile.getSampleFloat(0, 19, 0), 0f);
             assertEquals(  27.039f, tile.getSampleFloat(4, 10, 0), 0f);
+            /*
+             * Verify that the coverage is cached.
+             */
+            assertSame(coverage, store.read(null, null));
         }
     }
 }

Reply via email to