You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2019/10/22 19:19:22 UTC

[sis] branch geoapi-4.0 updated (dd58ea3 -> 31758f6)

This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git.


    from dd58ea3  Fix "llegalArgumentException: Argument ‘count’ shall not be negative" when invoking GridGeometry.toString().
     new be44417  'sis' command should resolve symbolic link.
     new 9581e07  Fix SampleDimension formatting bugs: - Was missing name of sample dimensions after the first one. - Formatting of bands without categories was not helpful.
     new 609bbd9  More explicit exception when there is overlapping range, and make this exception non-fatal in netCDF reader.
     new 31758f6  Avoid range collision if the minimum or maximum value in a netCDF file is equals to a pad/fill value.

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 application/sis-console/src/main/artifact/bin/sis  |   5 +-
 .../java/org/apache/sis/coverage/Category.java     |   8 +-
 .../java/org/apache/sis/coverage/CategoryList.java |   8 +-
 ...n.java => IllegalSampleDimensionException.java} |  23 ++--
 .../org/apache/sis/coverage/SampleDimension.java   |   5 +-
 .../org/apache/sis/coverage/SampleRangeFormat.java | 146 ++++++++++++---------
 .../main/java/org/apache/sis/coverage/ToNaN.java   |   4 +-
 .../java/org/apache/sis/coverage/package-info.java |   2 +-
 .../apache/sis/coverage/SampleRangeFormatTest.java | 137 +++++++++++++++++++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   3 +-
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   |   9 +-
 .../sis/internal/earth/netcdf/package-info.java    |   2 +-
 .../org/apache/sis/internal/netcdf/Convention.java |  48 +++++--
 .../apache/sis/internal/netcdf/RasterResource.java |  20 ++-
 .../org/apache/sis/internal/netcdf/Variable.java   |  12 +-
 15 files changed, 318 insertions(+), 114 deletions(-)
 copy core/sis-feature/src/main/java/org/apache/sis/coverage/{MismatchedCoverageRangeException.java => IllegalSampleDimensionException.java} (69%)
 create mode 100644 core/sis-feature/src/test/java/org/apache/sis/coverage/SampleRangeFormatTest.java


[sis] 03/04: More explicit exception when there is overlapping range, and make this exception non-fatal in netCDF reader.

Posted by de...@apache.org.
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 609bbd903788a05f9f7220d4bebfc40896a54ffb
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Oct 22 20:53:03 2019 +0200

    More explicit exception when there is overlapping range, and make this exception non-fatal in netCDF reader.
---
 .../java/org/apache/sis/coverage/Category.java     |  8 +--
 .../java/org/apache/sis/coverage/CategoryList.java |  8 +--
 .../coverage/IllegalSampleDimensionException.java  | 62 ++++++++++++++++++++++
 .../org/apache/sis/coverage/SampleDimension.java   |  5 +-
 .../main/java/org/apache/sis/coverage/ToNaN.java   |  4 +-
 .../apache/sis/internal/netcdf/RasterResource.java | 20 +++++--
 6 files changed, 92 insertions(+), 15 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
index ce35f0d..dfb2632 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/Category.java
@@ -69,7 +69,7 @@ import static java.lang.Double.doubleToRawLongBits;
  * <p>All {@code Category} objects are immutable and thread-safe.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -217,6 +217,8 @@ public class Category implements Serializable {
      *                  in the same {@link SampleDimension}.
      *                  The input is a real number in the {@code samples} range and the output shall be a unique value between
      *                  {@value MathFunctions#MIN_NAN_ORDINAL} and {@value MathFunctions#MAX_NAN_ORDINAL} inclusive.
+     * @throws IllegalSampleDimensionException if the {@code samples} range of values is empty
+     *         or the transfer function can not be used.
      */
     protected Category(final CharSequence name, NumberRange<?> samples, final MathTransform1D toUnits, final Unit<?> units,
              final DoubleToIntFunction toNaN)
@@ -237,7 +239,7 @@ public class Category implements Serializable {
          */
         if (!(minimum <= maximum)) {
             if (toUnits != null || !isNaN || doubleToRawLongBits(minimum) != doubleToRawLongBits(maximum)) {
-                throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalCategoryRange_2, name, samples));
+                throw new IllegalSampleDimensionException(Resources.format(Resources.Keys.IllegalCategoryRange_2, name, samples));
             }
         }
         if (isNaN) {
@@ -276,7 +278,7 @@ public class Category implements Serializable {
             range = samples;
             converse = new ConvertedCategory(this, toSamples, toUnits != null, units);
         } catch (TransformException e) {
-            throw new IllegalArgumentException(Resources.format(Resources.Keys.IllegalTransferFunction_1, name), e);
+            throw new IllegalSampleDimensionException(Resources.format(Resources.Keys.IllegalTransferFunction_1, name), e);
         }
     }
 
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
index 498bca6..f9cfe10 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -68,7 +68,7 @@ import static java.lang.Double.doubleToRawLongBits;
  * <p>Instances of {@link CategoryList} are immutable and thread-safe.</p>
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -162,7 +162,7 @@ final class CategoryList extends AbstractList<Category> implements MathTransform
      * @param  categories  the list of categories. This array is not cloned and is modified in-place.
      * @param  converse    if we are creating the list of categories after conversion from samples to real values,
      *                     the original list before conversion. Otherwise {@code null}.
-     * @throws IllegalArgumentException if two or more categories have overlapping sample value range.
+     * @throws IllegalSampleDimensionException if two or more categories have overlapping sample value range.
      */
     private CategoryList(final Category[] categories, CategoryList converse) {
         this.categories = categories;
@@ -229,7 +229,7 @@ final class CategoryList extends AbstractList<Category> implements MathTransform
             final Category previous = categories[i-1];
             final double   minimum  = minimums[i];
             if (Category.compare(minimum, previous.range.getMaxDouble(true)) <= 0) {
-                throw new IllegalArgumentException(Resources.format(Resources.Keys.CategoryRangeOverlap_4,
+                throw new IllegalSampleDimensionException(Resources.format(Resources.Keys.CategoryRangeOverlap_4,
                             previous.name, previous.getRangeLabel(),
                             category.name, category.getRangeLabel()));
             }
@@ -313,7 +313,7 @@ final class CategoryList extends AbstractList<Category> implements MathTransform
      * <p>This is defined as a static method for allowing the addition of a caching mechanism in the future if desired.</p>
      *
      * @param  categories  the list of categories. This array is not cloned and is modified in-place.
-     * @throws IllegalArgumentException if two or more categories have overlapping sample value range.
+     * @throws IllegalSampleDimensionException if two or more categories have overlapping sample value range.
      */
     static CategoryList create(final Category[] categories) {
         return new CategoryList(categories, null);
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/IllegalSampleDimensionException.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/IllegalSampleDimensionException.java
new file mode 100644
index 0000000..ec6003a
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/IllegalSampleDimensionException.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+
+/**
+ * Thrown when {@link SampleDimension} can not be created.
+ * The most common cause is overlapping {@linkplain Category#getSampleRange() ranges of sample values}.
+ * This exception is caused by an illegal argument, but may happen at a later stage
+ * (for example when {@link SampleDimension.Builder#build()} is invoked).
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public class IllegalSampleDimensionException extends IllegalArgumentException {
+    /**
+     * Serial number for inter-operability with different versions.
+     */
+    private static final long serialVersionUID = 6564945210063482395L;
+
+    /**
+     * Creates an exception with no message.
+     */
+    public IllegalSampleDimensionException() {
+        super();
+    }
+
+    /**
+     * Creates an exception with the specified message.
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     */
+    public IllegalSampleDimensionException(final String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an exception with the specified message and cause.
+     *
+     * @param message  the detail message, saved for later retrieval by the {@link #getMessage()} method.
+     * @param cause    the cause, saved for later retrieval by the {@link #getCause()} method.
+     */
+    public IllegalSampleDimensionException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
+}
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 f86e825..4918ed8 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
@@ -68,7 +68,7 @@ import org.apache.sis.util.Debug;
  * not used in same time.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see org.opengis.metadata.content.SampleDimension
  *
@@ -160,6 +160,7 @@ public class SampleDimension implements Serializable {
      * @param name        an identification for the sample dimension.
      * @param background  the background value, or {@code null} if none.
      * @param categories  the list of categories. May be empty if none.
+     * @throws IllegalSampleDimensionException if two or more categories have overlapping sample value range.
      */
     public SampleDimension(final GenericName name, final Number background, final Collection<? extends Category> categories) {
         ArgumentChecks.ensureNonNull("name", name);
@@ -976,6 +977,8 @@ public class SampleDimension implements Serializable {
          * Creates a new sample with the properties defined to this builder.
          *
          * @return the sample dimension.
+         * @throws IllegalSampleDimensionException if there is overlapping {@linkplain Category#getSampleRange()
+         *         ranges of sample values} or other problems that prevent the construction of sample dimensions.
          */
         public SampleDimension build() {
             GenericName name = dimensionName;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/ToNaN.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/ToNaN.java
index ffee34f..e9f584e 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/ToNaN.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/ToNaN.java
@@ -29,7 +29,7 @@ import org.apache.sis.internal.feature.Resources;
  * {@link Category} constructor.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -102,7 +102,7 @@ search: if (!add(ordinal)) {
                 do if (add(--ordinal)) break search;
                 while (ordinal > MathFunctions.MIN_NAN_ORDINAL);
             }
-            throw new IllegalStateException(Resources.format(Resources.Keys.TooManyQualitatives));
+            throw new IllegalSampleDimensionException(Resources.format(Resources.Keys.TooManyQualitatives));
         }
         return ordinal;
     }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index 20a8eea..d632b66 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -40,6 +40,7 @@ import org.apache.sis.coverage.grid.GridCoverage;
 import org.apache.sis.coverage.grid.GridGeometry;
 import org.apache.sis.coverage.grid.GridDerivation;
 import org.apache.sis.coverage.grid.GridRoundingMode;
+import org.apache.sis.coverage.IllegalSampleDimensionException;
 import org.apache.sis.storage.netcdf.AttributeNames;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
@@ -63,7 +64,7 @@ import org.apache.sis.internal.jdk9.JDK9;
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -375,14 +376,22 @@ public final class RasterResource extends AbstractGridResource implements Resour
             synchronized (lock) {
                 for (int i=0; i<ranges.length; i++) {
                     if (ranges[i] == null) {
-                        if (builder == null) builder = new SampleDimension.Builder();
-                        ranges[i] = createSampleDimension(builder, getVariable(i), i);
+                        if (builder == null) {
+                            builder = new SampleDimension.Builder();
+                        }
+                        try {
+                            ranges[i] = createSampleDimension(builder, getVariable(i), i);
+                        } catch (TransformException | IllegalSampleDimensionException e) {
+                            builder.categories().clear();
+                            ranges[i] = builder.build();
+                            warning(e);
+                        }
                         builder.clear();
                     }
                 }
             }
-        } catch (TransformException e) {
-            throw new DataStoreReferencingException(e);
+        } catch (RuntimeException e) {
+            throw new DataStoreContentException(e);
         }
         return UnmodifiableArrayList.wrap(ranges);
     }
@@ -393,6 +402,7 @@ public final class RasterResource extends AbstractGridResource implements Resour
      * @param  builder  the builder to use for creating the sample dimension.
      * @param  band     the data for which to create a sample dimension.
      * @param  index    index in the variable dimension identified by {@link #bandDimension}.
+     * @throws IllegalSampleDimensionException if a sample dimension has overlapping ranges.
      * @throws TransformException if an error occurred while using the transfer function.
      */
     private SampleDimension createSampleDimension(final SampleDimension.Builder builder, final Variable band, final int index)


[sis] 01/04: 'sis' command should resolve symbolic link.

Posted by de...@apache.org.
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 be4441711b5868cbf81b148044437abe04d76289
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Oct 22 16:03:04 2019 +0200

    'sis' command should resolve symbolic link.
---
 application/sis-console/src/main/artifact/bin/sis | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/application/sis-console/src/main/artifact/bin/sis b/application/sis-console/src/main/artifact/bin/sis
index ed98e33..691d163 100755
--- a/application/sis-console/src/main/artifact/bin/sis
+++ b/application/sis-console/src/main/artifact/bin/sis
@@ -16,7 +16,10 @@
 # limitations under the License.
 # ------------------------------------------------------------------------
 
-BASE_DIR="`dirname $0`/.."
+set -o errexit
+
+BASE_DIR="`readlink --canonicalize-existing $0`"
+BASE_DIR="`dirname $BASE_DIR`/.."
 SIS_DATA="${SIS_DATA:-$BASE_DIR/data}"
 export SIS_DATA
 


[sis] 04/04: Avoid range collision if the minimum or maximum value in a netCDF file is equals to a pad/fill value.

Posted by de...@apache.org.
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 31758f6e637dfb0a5a6c92d177675fa6c0a799f8
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Oct 22 21:18:48 2019 +0200

    Avoid range collision if the minimum or maximum value in a netCDF file is equals to a pad/fill value.
---
 .../apache/sis/internal/earth/netcdf/GCOM_C.java   |  9 ++--
 .../sis/internal/earth/netcdf/package-info.java    |  2 +-
 .../org/apache/sis/internal/netcdf/Convention.java | 48 ++++++++++++++++------
 .../org/apache/sis/internal/netcdf/Variable.java   | 12 +++---
 4 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
index b74c9c4..bd14569 100644
--- a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
+++ b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/GCOM_C.java
@@ -110,7 +110,7 @@ import ucar.nc2.constants.CF;
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="http://global.jaxa.jp/projects/sat/gcom_c/">SHIKISAI (GCOM-C) on JAXA</a>
  * @see <a href="https://en.wikipedia.org/wiki/Global_Change_Observation_Mission">GCOM on Wikipedia</a>
@@ -430,11 +430,12 @@ public final class GCOM_C extends Convention {
      * Returns the range of valid values, or {@code null} if unknown.
      *
      * @param  data  the variable to get valid range of values for.
+     * @param  nodataValues  the fill values and padding values.
      * @return the range of valid values, or {@code null} if unknown.
      */
     @Override
-    public NumberRange<?> validRange(final Variable data) {
-        NumberRange<?> range = super.validRange(data);
+    public NumberRange<?> validRange(final Variable data, final Set<Number> nodataValues) {
+        NumberRange<?> range = super.validRange(data, nodataValues);
         if (range == null) {
             final double min = data.getAttributeAsNumber("Minimum_valid_DN");
             final double max = data.getAttributeAsNumber("Maximum_valid_DN");
@@ -470,7 +471,7 @@ public final class GCOM_C extends Convention {
 
     /**
      * Builds the function converting values from their packed formats in the variable to "real" values.
-     * This method is invoked only if {@link #validRange(Variable)} returned a non-null value.
+     * This method is invoked only if {@link #validRange(Variable, Set)} returned a non-null value.
      *
      * @param  data  the variable from which to determine the transfer function.
      * @return a transfer function built from the attributes defined in the given variable.
diff --git a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
index f353184..1381a2a 100644
--- a/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
+++ b/profiles/sis-japan-profile/src/main/java/org/apache/sis/internal/earth/netcdf/package-info.java
@@ -21,7 +21,7 @@
  *
  * @author  Alexis Manin (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 6310d20..03c3666 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -63,7 +63,7 @@ import ucar.nc2.constants.CF;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Alexis Manin (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="https://issues.apache.org/jira/browse/SIS-315">SIS-315</a>
  *
@@ -92,7 +92,7 @@ public class Convention {
     /**
      * Names of attributes where to fetch minimum and maximum sample values, in preference order.
      *
-     * @see #validRange(Variable)
+     * @see #validRange(Variable, Set)
      */
     private static final String[] RANGE_ATTRIBUTES = {
         "valid_range",      // Expected "reasonable" range for variable.
@@ -555,13 +555,13 @@ public class Convention {
      * Otherwise if this method returns the range of real values, then that range shall be an instance
      * of {@link MeasurementRange} for allowing the caller to distinguish the two cases.
      *
-     * @param  data  the variable to get valid range of values for.
-     *               This is usually a variable containing raster data.
+     * @param  data  the variable to get valid range of values for (usually a variable containing raster data).
+     * @param  nodataValues  the fill values and padding values.
      * @return the range of valid values, or {@code null} if unknown.
      *
      * @see Variable#getRangeFallback()
      */
-    public NumberRange<?> validRange(final Variable data) {
+    public NumberRange<?> validRange(final Variable data, final Set<Number> nodataValues) {
         Number minimum = null;
         Number maximum = null;
         Class<? extends Number> type = null;
@@ -590,23 +590,47 @@ public class Convention {
                     data.decoder.illegalAttributeValue(attribute, values.stringValue(i), e);
                 }
             }
+            /*
+             * Stop the loop and return a range as soon as we have enough information.
+             * Note that we may loop over many attributes before to complete information.
+             */
             if (minimum != null && maximum != null) {
                 /*
                  * Heuristic rule defined in UCAR documentation (see EnhanceScaleMissing interface):
                  * if the type of the range is equal to the type of the scale, and the type of the
                  * data is not wider, then assume that the minimum and maximum are real values.
                  */
+                final Class<?> scaleType  = data.getAttributeType(CDM.SCALE_FACTOR);
+                final Class<?> offsetType = data.getAttributeType(CDM.ADD_OFFSET);
                 final int rangeType = Numbers.getEnumConstant(type);
-                if (rangeType >= data.getDataType().number &&
-                    rangeType >= Math.max(Numbers.getEnumConstant(data.getAttributeType(CDM.SCALE_FACTOR)),
-                                          Numbers.getEnumConstant(data.getAttributeType(CDM.ADD_OFFSET))))
+                if ((scaleType != null || offsetType != null)
+                        && rangeType >= data.getDataType().number
+                        && rangeType >= Math.max(Numbers.getEnumConstant(scaleType),
+                                                 Numbers.getEnumConstant(offsetType)))
                 {
                     @SuppressWarnings({"unchecked", "rawtypes"})
                     final NumberRange<?> range = new MeasurementRange(type, minimum, true, maximum, true, data.getUnit());
                     return range;
                 } else {
+                    /*
+                     * The range use sample values (before conversion to the unit of measurement).
+                     * Before to return that range, check if the minimum or maximum overlaps with
+                     * a pad value. If this is the case, resolve the overlapping by making that
+                     * value exclusive instead than inclusive.
+                     */
+                    boolean isMinIncluded = true;
+                    boolean isMaxIncluded = true;
+                    if (!nodataValues.isEmpty()) {
+                        final double minValue = minimum.doubleValue();
+                        final double maxValue = maximum.doubleValue();
+                        for (final Number pad : nodataValues) {
+                            final double value = pad.doubleValue();
+                            isMinIncluded &= (minValue != value);
+                            isMaxIncluded &= (maxValue != value);
+                        }
+                    }
                     @SuppressWarnings({"unchecked", "rawtypes"})
-                    final NumberRange<?> range = new NumberRange(type, minimum, true, maximum, true);
+                    final NumberRange<?> range = new NumberRange(type, minimum, isMinIncluded, maximum, isMaxIncluded);
                     return range;
                 }
             }
@@ -616,7 +640,7 @@ public class Convention {
 
     /**
      * Compares two numbers which shall be of the same class.
-     * This is a helper method for {@link #validRange(Variable)}.
+     * This is a helper method for {@link #validRange(Variable, Set)}.
      */
     @SuppressWarnings("unchecked")
     private static int compare(final Number n1, final Number n2) {
@@ -669,8 +693,8 @@ public class Convention {
      * to be created for each variable.
      *
      * <p>This method is invoked in contexts where a transfer function is assumed to exist, for example
-     * because {@link #validRange(Variable)} returned a non-null value. Consequently this method shall
-     * never return {@code null}, but can return the identity function.</p>
+     * because {@link #validRange(Variable, Set)} returned a non-null value. Consequently this method
+     * shall never return {@code null}, but can return the identity function.</p>
      *
      * @param  data  the variable from which to determine the transfer function.
      *               This is usually a variable containing raster data.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
index 308f47d..8860b1b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
@@ -787,14 +787,14 @@ public abstract class Variable extends Node {
 
     /**
      * Returns the range of valid values, or {@code null} if unknown. This is a shortcut for
-     * {@link Convention#validRange(Variable)} with a fallback on {@link #getRangeFallback()}.
+     * {@link Convention#validRange(Variable, Set)} with a fallback on {@link #getRangeFallback()}.
      *
      * @return the range of valid values, or {@code null} if unknown.
      *
-     * @see Convention#validRange(Variable)
+     * @see Convention#validRange(Variable, Set)
      */
     final NumberRange<?> getValidRange() {
-        NumberRange<?> range = decoder.convention().validRange(this);
+        NumberRange<?> range = decoder.convention().validRange(this, getNodataValues().keySet());
         if (range == null) {
             range = getRangeFallback();
         }
@@ -803,8 +803,8 @@ public abstract class Variable extends Node {
 
     /**
      * Returns the range of values as determined by the data type or other means, or {@code null} if unknown.
-     * This method is invoked only as a fallback if {@link Convention#validRange(Variable)} did not found a
-     * range of values by application of CF conventions. The returned range may be a range of packed values
+     * This method is invoked only as a fallback if {@link Convention#validRange(Variable, Set)} did not found
+     * a range of values by application of CF conventions. The returned range may be a range of packed values
      * or a range of real values. In the later case, the range shall be an instance of
      * {@link org.apache.sis.measure.MeasurementRange}.
      *
@@ -814,7 +814,7 @@ public abstract class Variable extends Node {
      *
      * @return the range of valid values, or {@code null} if unknown.
      *
-     * @see Convention#validRange(Variable)
+     * @see Convention#validRange(Variable, Set)
      */
     protected NumberRange<?> getRangeFallback() {
         final DataType dataType = getDataType();


[sis] 02/04: Fix SampleDimension formatting bugs: - Was missing name of sample dimensions after the first one. - Formatting of bands without categories was not helpful.

Posted by de...@apache.org.
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 9581e074b95322359d71bdab7369cbb22036cf34
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Oct 22 20:49:00 2019 +0200

    Fix SampleDimension formatting bugs:
    - Was missing name of sample dimensions after the first one.
    - Formatting of bands without categories was not helpful.
---
 .../org/apache/sis/coverage/SampleRangeFormat.java | 146 ++++++++++++---------
 .../java/org/apache/sis/coverage/package-info.java |   2 +-
 .../apache/sis/coverage/SampleRangeFormatTest.java | 137 +++++++++++++++++++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   3 +-
 4 files changed, 225 insertions(+), 63 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
index 1a7865e..e697aa0 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
@@ -35,7 +35,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * Formats the range of a category. This is used for {@link SampleDimension#toString()} implementation.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
@@ -66,6 +66,11 @@ final class SampleRangeFormat extends RangeFormat {
     private boolean hasQuantitative;
 
     /**
+     * Whether at least one category of any type has been found in at least one sample dimension.
+     */
+    private boolean hasCategory;
+
+    /**
      * The localize resources for table header. Words will be "Values", "Measures" and "Name".
      */
     private final Vocabulary words;
@@ -95,6 +100,7 @@ final class SampleRangeFormat extends RangeFormat {
         numFractionDigits = new int[count];
         hasPackedValues   = false;
         hasQuantitative   = false;
+        hasCategory       = false;
         for (int i=0; i<count; i++) {
             int ndigits = 0;
             for (final Category category : dimensions[i].getCategories()) {
@@ -107,6 +113,7 @@ final class SampleRangeFormat extends RangeFormat {
                 final boolean isPacked = (Double.doubleToRawLongBits(smin) != Double.doubleToRawLongBits(cmin))
                                        | (Double.doubleToRawLongBits(smax) != Double.doubleToRawLongBits(cmax));
                 hasPackedValues |= isPacked;
+                hasCategory = true;
                 /*
                  * If the sample values are already real values, pretend that they are packed in bytes.
                  * The intent is only to compute an arbitrary number of fraction digits.
@@ -209,36 +216,45 @@ final class SampleRangeFormat extends RangeFormat {
         if (hasPackedValues) table.append(words.getString(Vocabulary.Keys.Values))  .nextColumn();
         if (hasQuantitative) table.append(words.getString(Vocabulary.Keys.Measures)).nextColumn();
         /* Unconditional  */ table.append(words.getString(Vocabulary.Keys.Name))    .nextLine();
-        table.nextLine('═');
-        table.append('#');                      // Dummy character to be replaced by band name later.
-        table.appendHorizontalSeparator();
-        for (final SampleDimension dim : dimensions) {
-            for (final Category category : dim.getCategories()) {
-                /*
-                 * "Sample values" column. Omitted if all values are already real values.
-                 */
-                if (hasPackedValues) {
-                    table.setCellAlignment(TableAppender.ALIGN_RIGHT);
-                    table.append(formatSample(category.getRangeLabel()));
-                    table.nextColumn();
-                }
-                table.setCellAlignment(TableAppender.ALIGN_LEFT);
-                /*
-                 * "Real values" column. Omitted if no category has a transfer function.
-                 */
-                if (hasQuantitative) {
-                    final Category converted = category.converted();
-                    final String text;
-                    if (converted.isConvertedQualitative()) {
-                        text = String.valueOf(converted.getRangeLabel());       // Example: NaN #0
-                    } else {
-                        text = formatMeasure(converted.getSampleRange());       // Example: [6.0 … 25.0)°C
+        if (!hasCategory) {
+            table.nextLine('═');
+            table.setCellAlignment(TableAppender.ALIGN_LEFT);
+            for (final SampleDimension dim : dimensions) {
+                table.append(getName(dim));
+                table.nextLine();
+            }
+        } else {
+            for (final SampleDimension dim : dimensions) {
+                table.nextLine('═');
+                table.append('#');                      // Dummy character to be replaced by band name later.
+                table.appendHorizontalSeparator();
+                for (final Category category : dim.getCategories()) {
+                    /*
+                     * "Sample values" column. Omitted if all values are already real values.
+                     */
+                    if (hasPackedValues) {
+                        table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+                        table.append(formatSample(category.getRangeLabel()));
+                        table.nextColumn();
+                    }
+                    table.setCellAlignment(TableAppender.ALIGN_LEFT);
+                    /*
+                     * "Real values" column. Omitted if no category has a transfer function.
+                     */
+                    if (hasQuantitative) {
+                        final Category converted = category.converted();
+                        final String text;
+                        if (converted.isConvertedQualitative()) {
+                            text = String.valueOf(converted.getRangeLabel());       // Example: NaN #0
+                        } else {
+                            text = formatMeasure(converted.getSampleRange());       // Example: [6.0 … 25.0)°C
+                        }
+                        table.append(text);
+                        table.nextColumn();
                     }
-                    table.append(text);
-                    table.nextColumn();
+                    table.append(category.getName().toString(getLocale()));
+                    table.nextLine();
                 }
-                table.append(category.getName().toString(getLocale()));
-                table.nextLine();
             }
         }
         table.appendHorizontalSeparator();
@@ -253,44 +269,52 @@ final class SampleRangeFormat extends RangeFormat {
          * a value on many cells. The following code changes some characters but do not change buffer
          * length.
          */
-        int lastDimensionEnd = 0;
-        final String lineSeparator = table.getLineSeparator();
-        final String toSearch = lineSeparator + '╞';
-        for (final SampleDimension dim : dimensions) {
-            int lineStart = buffer.indexOf(toSearch, lastDimensionEnd);
-            if (lineStart < 0) break;                                           // Should not happen.
-            lineStart += toSearch.length();
-            int i = replace(buffer, lineStart, '╪', '╧', '╡');
-            int limit = (i-2) - lineStart;                                      // Space available in a row.
-            i += lineSeparator.length() + 2;                                    // Beginning of next line.
-            /*
-             * At this point, 'i' is at the beginning of the row where to format the band name.
-             * The line above that row has been modified for removing vertical lines. Now fill
-             * the space in current row with band name and pad with white spaces.
-             */
-            final GenericName name = dim.getName();
-            String label;
-            if (name != null) {
-                label = name.toInternationalString().toString(getLocale());
-            } else {
-                label = words.getString(Vocabulary.Keys.Unnamed);
-            }
-            if (label.length() > limit) {
-                label = label.substring(0, limit);
+        if (hasCategory) {
+            int lastDimensionEnd = 0;
+            final String lineSeparator = table.getLineSeparator();
+            final String toSearch = lineSeparator + '╞';
+            for (final SampleDimension dim : dimensions) {
+                int lineStart = buffer.indexOf(toSearch, lastDimensionEnd);
+                if (lineStart < 0) break;                                           // Should not happen.
+                lineStart += toSearch.length();
+                int i = replace(buffer, lineStart, '╪', '╧', '╡');
+                int limit = (i-2) - lineStart;                                      // Space available in a row.
+                i += lineSeparator.length() + 2;                                    // Beginning of next line.
+                /*
+                 * At this point, 'i' is at the beginning of the row where to format the band name.
+                 * The line above that row has been modified for removing vertical lines. Now fill
+                 * the space in current row with band name and pad with white spaces.
+                 */
+                String name = getName(dim);
+                if (name.length() > limit) {
+                    name = name.substring(0, limit);
+                }
+                limit += i;                                         // Now an absolute index instead than a length.
+                buffer.replace(i, i += name.length(), name);
+                while (i < limit) buffer.setCharAt(i++, ' ');
+                /*
+                 * At this point the sample dimension name has been written.
+                 * Update the next line and move to the next sample dimension.
+                 */
+                lastDimensionEnd = replace(buffer, i + lineSeparator.length() + 2, '┼', '┬', '┤');
             }
-            limit += i;                                         // Now an absolute index instead than a length.
-            buffer.replace(i, i += label.length(), label);
-            while (i < limit) buffer.setCharAt(i++, ' ');
-            /*
-             * At this point the sample dimension name has been written.
-             * Update the next line and move to the next sample dimension.
-             */
-            lastDimensionEnd = replace(buffer, i + lineSeparator.length() + 2, '┼', '┬', '┤');
         }
         return buffer.toString();
     }
 
     /**
+     * Returns the localized name for the given dimension, or "unnamed" if none.
+     */
+    private String getName(final SampleDimension dim) {
+        final GenericName name = dim.getName();
+        if (name != null) {
+            return name.toInternationalString().toString(getLocale());
+        } else {
+            return words.getString(Vocabulary.Keys.Unnamed);
+        }
+    }
+
+    /**
      * Replaces characters in the given buffer until a sentinel value, which must exist.
      *
      * @param  buffer   the buffer where to perform the replacements.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/coverage/package-info.java b/core/sis-feature/src/main/java/org/apache/sis/coverage/package-info.java
index 6bdf0cc..2b67959 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/coverage/package-info.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/coverage/package-info.java
@@ -23,7 +23,7 @@
  * {@link org.apache.sis.coverage.grid}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   1.0
  * @module
  */
diff --git a/core/sis-feature/src/test/java/org/apache/sis/coverage/SampleRangeFormatTest.java b/core/sis-feature/src/test/java/org/apache/sis/coverage/SampleRangeFormatTest.java
new file mode 100644
index 0000000..c88cd31
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/coverage/SampleRangeFormatTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import java.util.Locale;
+import org.apache.sis.measure.Units;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests {@link SampleDimension#toString(Locale, SampleDimension...)}.
+ * Note that the formatting may change in any future version.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+public final strictfp class SampleRangeFormatTest extends TestCase {
+    /**
+     * Creates a band for temperature data.
+     */
+    private static SampleDimension temperature(final SampleDimension.Builder builder) {
+        builder.clear();
+        return builder.setName("Temperature").addQualitative("No data", 0)
+               .addQuantitative("Value", 1, 256, 0.15, -5, Units.CELSIUS).build();
+    }
+
+    /**
+     * Creates a band for salinity data.
+     */
+    private static SampleDimension salinity(final SampleDimension.Builder builder) {
+        builder.clear();
+        return builder.setName("Salinity").addQualitative("No data", 0)
+               .addQuantitative("Value", 1, 256, 0.01, 20, Units.PSU).build();
+    }
+
+    /**
+     * Tests formatting of a single band with some categories.
+     */
+    @Test
+    public void testSingleBand() {
+        final SampleDimension.Builder builder = new SampleDimension.Builder();
+        final String text = SampleDimension.toString(Locale.US, temperature(builder));
+        assertMultilinesEquals(
+                "┌───────────┬─────────────────┬─────────┐\n" +
+                "│  Values   │    Measures     │  Name   │\n" +
+                "╞═══════════╧═════════════════╧═════════╡\n" +
+                "│ Temperature                           │\n" +
+                "├───────────┬─────────────────┬─────────┤\n" +
+                "│        0  │ NaN #0          │ No data │\n" +
+                "│ [1 … 256) │ [-4.8 … 33.4)°C │ Value   │\n" +
+                "└───────────┴─────────────────┴─────────┘\n", text);
+    }
+
+    /**
+     * Tests formatting of more than one band.
+     */
+    @Test
+    public void testMultiBands() {
+        final SampleDimension.Builder builder = new SampleDimension.Builder();
+        final String text = SampleDimension.toString(Locale.US, temperature(builder), salinity(builder));
+        assertMultilinesEquals(
+                "┌───────────┬───────────────────┬─────────┐\n" +
+                "│  Values   │     Measures      │  Name   │\n" +
+                "╞═══════════╧═══════════════════╧═════════╡\n" +
+                "│ Temperature                             │\n" +
+                "├───────────┬───────────────────┬─────────┤\n" +
+                "│        0  │ NaN #0            │ No data │\n" +
+                "│ [1 … 256) │ [-4.8 … 33.4)°C   │ Value   │\n" +
+                "╞═══════════╧═══════════════════╧═════════╡\n" +
+                "│ Salinity                                │\n" +
+                "├───────────┬───────────────────┬─────────┤\n" +
+                "│        0  │ NaN #0            │ No data │\n" +
+                "│ [1 … 256) │ [20.0 … 22.6) psu │ Value   │\n" +
+                "└───────────┴───────────────────┴─────────┘\n", text);
+    }
+
+    /**
+     * Tests formatting of sample dimensions having only qualitative categories.
+     */
+    @Test
+    public void testQualitativeOnly() {
+        final SampleDimension land = new SampleDimension.Builder().setName("Land use")
+                .addQualitative("Lake",   0)
+                .addQualitative("Forest", 1)
+                .addQualitative("Urban",  2).build();
+        final String text = SampleDimension.toString(Locale.US, land);
+        assertMultilinesEquals(
+                "┌────────┬────────┐\n" +
+                "│ Values │  Name  │\n" +
+                "╞════════╧════════╡\n" +
+                "│ Land use        │\n" +
+                "├────────┬────────┤\n" +
+                "│     0  │ Lake   │\n" +
+                "│     1  │ Forest │\n" +
+                "│     2  │ Urban  │\n" +
+                "└────────┴────────┘\n", text);
+    }
+
+    /**
+     * Tests formatting of sample dimensions having only names.
+     */
+    @Test
+    public void testNameOnly() {
+        final SampleDimension.Builder builder = new SampleDimension.Builder();
+        final SampleDimension red   = builder.setName("Red"  ).build(); builder.clear();
+        final SampleDimension green = builder.setName("Green").build(); builder.clear();
+        final SampleDimension blue  = builder.setName("Blue" ).build(); builder.clear();
+        final String text = SampleDimension.toString(Locale.US, red, green, blue);
+        assertMultilinesEquals(
+                "┌───────┐\n" +
+                "│ Name  │\n" +
+                "╞═══════╡\n" +
+                "│ Red   │\n" +
+                "│ Green │\n" +
+                "│ Blue  │\n" +
+                "└───────┘\n", text);
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index 7c06225..ad0cd8b 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -26,7 +26,7 @@ import org.junit.runners.Suite;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  * @since   0.5
  * @module
  */
@@ -79,6 +79,7 @@ import org.junit.runners.Suite;
     org.apache.sis.coverage.CategoryTest.class,
     org.apache.sis.coverage.CategoryListTest.class,
     org.apache.sis.coverage.SampleDimensionTest.class,
+    org.apache.sis.coverage.SampleRangeFormatTest.class,
     org.apache.sis.internal.coverage.ScaledColorSpaceTest.class,
     org.apache.sis.internal.coverage.BufferedGridCoverageTest.class
 })