sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 02/02: Add more tests.
Date Tue, 04 Dec 2018 20:13:34 GMT
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 66a0c8714ae283384f77aaec63504f68f7d82e7e
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Dec 4 21:13:20 2018 +0100

    Add more tests.
---
 .../java/org/apache/sis/coverage/Category.java     |   8 +-
 .../java/org/apache/sis/coverage/CategoryList.java |   2 +-
 .../org/apache/sis/coverage/SampleDimension.java   | 120 +++++++++++++++------
 .../org/apache/sis/internal/raster/Resources.java  |  10 ++
 .../sis/internal/raster/Resources.properties       |   2 +
 .../sis/internal/raster/Resources_fr.properties    |   2 +
 .../apache/sis/coverage/SampleDimensionTest.java   |  73 +++++++++++++
 .../org/apache/sis/test/suite/RasterTestSuite.java |   3 +-
 .../org/apache/sis/measure/MeasurementRange.java   |  18 ++++
 .../main/java/org/apache/sis/measure/Range.java    |  19 +++-
 10 files changed, 219 insertions(+), 38 deletions(-)

diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
index e744602..013326d 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/Category.java
@@ -345,17 +345,17 @@ search:         if (!padValues.add(ordinal)) {
 
     /**
      * Returns the range of values after conversions by the transfer function.
-     * If a unit of measurement is available, then this range will be an instance of {@link
MeasurementRange}.
-     * This range is absent if there is no transfer function, i.e. this category is qualitative.
+     * This range is absent if there is no transfer function, i.e. if this category is qualitative.
      *
      * @return the range of values after conversion by the transfer function.
      *
      * @see SampleDimension#getMeasurementRange()
      */
-    public Optional<NumberRange<?>> getMeasurementRange() {
+    public Optional<MeasurementRange<?>> getMeasurementRange() {
         // Same assumption than in 'isQuantitative()'.
         assert isPublic() : this;
-        return Optional.ofNullable(converted.range);
+        // A ClassCastException below would be a bug in our constructor.
+        return Optional.ofNullable((MeasurementRange<?>) converted.range);
     }
 
     /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
index a973632..1fcc080 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/CategoryList.java
@@ -86,7 +86,7 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
      * The "main" category, or {@code null} if there is none. The main category
      * is the quantitative category with the widest range of sample values.
      */
-    private final Category main;
+    final Category main;
 
     /**
      * The category to use if {@link #search(double)} is invoked with a sample value greater
than all ranges in this list.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
index cb9301a..8b0bbbc 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleDimension.java
@@ -19,6 +19,7 @@ package org.apache.sis.coverage;
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Collections;
@@ -27,6 +28,7 @@ import javax.measure.Unit;
 import org.opengis.util.InternationalString;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.apache.sis.referencing.operation.transform.TransferFunction;
+import org.apache.sis.internal.raster.Resources;
 import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.resources.Vocabulary;
@@ -62,7 +64,10 @@ import org.apache.sis.util.Numbers;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @version 1.0
- * @since   1.0
+ *
+ * @see org.opengis.metadata.content.SampleDimension
+ *
+ * @since 1.0
  * @module
  */
 public final class SampleDimension implements Serializable {
@@ -133,25 +138,41 @@ public final class SampleDimension implements Serializable {
     /**
      * Returns the values to indicate "no data" for this sample dimension.
      *
-     * @return the values to indicate no data values for this sample dimension, or an empty
list if none.
+     * @return the values to indicate no data values for this sample dimension, or an empty
set if none.
+     * @throws IllegalStateException if this method can not expand the range of no data values,
for example
+     *         because some ranges contain an infinite amount of values.
      */
-    public List<Number> getNoDataValues() {
+    public Set<Number> getNoDataValues() {
         if (!categories.hasQuantitative()) {
-            return Collections.emptyList();
+            return Collections.emptySet();
         }
-        final List<Number> noDataValues = new ArrayList<>(categories.size());
+        final NumberRange<?>[] ranges = new NumberRange<?>[categories.size()];
+        Class<? extends Number> widestClass = Byte.class;
+        int count = 0;
         for (final Category c : categories) {
             if (!c.isQuantitative()) {
-                final NumberRange<?> r = c.range;
-                final Number value;
-                if (r.isMinIncluded()) {
-                    value = r.getMinDouble();
-                } else if (r.isMaxIncluded()) {
-                    value = r.getMaxDouble();
-                } else {
-                    value = (c.minimum + c.maximum) / 2;
+                if (!c.range.isBounded()) {
+                    throw new IllegalStateException(Resources.format(Resources.Keys.CanNotEnumerateValuesInRange_1,
c.range));
+                }
+                widestClass = Numbers.widestClass(widestClass, c.range.getElementType());
+                ranges[count++] = c.range;
+            }
+        }
+        final Set<Number> noDataValues = new TreeSet<>();
+        for (int i=0; i<count; i++) {
+            final NumberRange<?> range = ranges[i];
+            final Number minimum = range.getMinValue();
+            final Number maximum = range.getMaxValue();
+            if (range.isMinIncluded()) noDataValues.add(Numbers.cast(minimum, widestClass));
+            if (range.isMaxIncluded()) noDataValues.add(Numbers.cast(maximum, widestClass));
+            if (Numbers.isInteger(range.getElementType())) {
+                long value = minimum.longValue() + 1;       // If value was inclusive, then
it has already been added to the set.
+                long stop  = maximum.longValue() - 1;
+                while (value <= stop) {
+                    noDataValues.add(Numbers.wrap(value, widestClass));
                 }
-                noDataValues.add(value);
+            } else if (!minimum.equals(maximum)) {
+                throw new IllegalStateException(Resources.format(Resources.Keys.CanNotEnumerateValuesInRange_1,
range));
             }
         }
         return noDataValues;
@@ -172,56 +193,93 @@ public final class SampleDimension implements Serializable {
 
     /**
      * Returns the range of values after conversions by the transfer function.
-     * If a unit of measurement is available, then this range will be an instance of {@link
MeasurementRange}.
      * This range is absent if there is no transfer function.
      *
      * @return the range of values after conversion by the transfer function.
+     *
+     * @see #getUnits()
      */
-    public Optional<NumberRange<?>> getMeasurementRange() {
-        return Optional.ofNullable(categories.converted.range);
+    public Optional<MeasurementRange<?>> getMeasurementRange() {
+        // A ClassCastException below would be a bug in our constructors.
+        return Optional.ofNullable((MeasurementRange<?>) categories.converted.range);
     }
 
     /**
      * Returns the <cite>transfer function</cite> from sample values to real
values.
      * This method returns a transform expecting sample values as input and computing real
values as output.
      * The output units of measurement is given by {@link #getUnits()}.
-     * This transform will take care of converting all "{@linkplain #getNoDataValues() no
data values}" into {@code NaN} values.
+     *
+     * <p>This transform takes care of converting all "{@linkplain #getNoDataValues()
no data values}" into {@code NaN} values.
      * The <code>transferFunction.{@linkplain MathTransform1D#inverse() inverse()}</code>
transform is capable to differentiate
-     * {@code NaN} values to get back the original sample value.
+     * those {@code NaN} values and get back the original sample value.</p>
      *
      * @return the <cite>transfer function</cite> from sample to real values.
May be absent if this sample dimension
-     *         do not defines any transform (which is not the same that defining an identity
transform).
-     *
-     * @see TransferFunction
+     *         does not define any transform (which is not the same that defining an identity
transform).
      */
     public Optional<MathTransform1D> getTransferFunction() {
         return Optional.ofNullable(transferFunction);
     }
 
     /**
+     * Returns the offset and scale factors of the transfer function.
+     * The formula returned by this method does <strong>not</strong> take
+     * "{@linkplain #getNoDataValues() no data values}" in account.
+     * For a more generic transfer function, see {@link #getTransferFunction()}.
+     *
+     * @return a description of the part of the transfer function working on real numbers.
+     * @throws IllegalStateException if the transfer function can not be simplified in a
form representable
+     *         by {@link TransferFunction}.
+     */
+    public Optional<TransferFunction> getTransferFunctionFormula() {
+        MathTransform1D tr = null;
+        for (final Category category : categories) {
+            if (category.isQuantitative()) {
+                if (tr == null) {
+                    tr = category.transferFunction;
+                } else if (!tr.equals(category.transferFunction)) {
+                    throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSimplifyTransferFunction_1));
+                }
+            }
+        }
+        if (tr == null) {
+            return Optional.empty();
+        }
+        final TransferFunction f = new TransferFunction();
+        try {
+            f.setTransform(tr);
+        } catch (IllegalArgumentException e) {
+            throw new IllegalStateException(Resources.format(Resources.Keys.CanNotSimplifyTransferFunction_1,
e));
+        }
+        return Optional.of(f);
+    }
+
+    /**
      * Returns the units of measurement for this sample dimension.
      * This unit applies to values obtained after the {@linkplain #getTransferFunction()
transfer function}.
      * May be absent if not applicable.
      *
      * @return the units of measurement.
+     * @throws IllegalStateException if this sample dimension use different units.
+     *
+     * @see #getMeasurementRange()
      */
     public Optional<Unit<?>> getUnits() {
-        Unit<?> units = null;
+        Unit<?> main = null;
         for (final Category c : categories.converted) {
             final NumberRange<?> r = c.range;
             if (r instanceof MeasurementRange<?>) {
-                final Unit<?> u = ((MeasurementRange<?>) r).unit();
-                if (u != null) {
-                    if (units == null) {
-                        units = u;
-                    } else if (!units.equals(u)) {
-                        units = null;                   // Different quantitative categories
use different units.
-                        break;
+                final Unit<?> unit = ((MeasurementRange<?>) r).unit();
+                if (unit != null) {
+                    if (main != null && !main.equals(unit)) {
+                        throw new IllegalStateException();
+                    }
+                    if (main == null || c == categories.converted.main) {
+                        main = unit;
                     }
                 }
             }
         }
-        return Optional.ofNullable(units);
+        return Optional.ofNullable(main);
     }
 
     /**
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
index 6eeaa30..9a7ef3f 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.java
@@ -59,11 +59,21 @@ public final class Resources extends IndexedResourceBundle {
         }
 
         /**
+         * Can not enumerate values in the {0} range.
+         */
+        public static final short CanNotEnumerateValuesInRange_1 = 18;
+
+        /**
          * Some envelope dimensions can not be mapped to grid dimensions.
          */
         public static final short CanNotMapToGridDimensions = 12;
 
         /**
+         * Can not simplify transfer function of sample dimension “{0}”.
+         */
+        public static final short CanNotSimplifyTransferFunction_1 = 19;
+
+        /**
          * The two categories “{0}” and “{2}” have overlapping ranges: {1} and {3}
respectively.
          */
         public static final short CategoryRangeOverlap_4 = 13;
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
index 2c2f5a6..53cdae1 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources.properties
@@ -19,7 +19,9 @@
 # Resources in this file are for "sis-raster" usage only and should not be used by any other
module.
 # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources"
package.
 #
+CanNotEnumerateValuesInRange_1    = Can not enumerate values in the {0} range.
 CanNotMapToGridDimensions         = Some envelope dimensions can not be mapped to grid dimensions.
+CanNotSimplifyTransferFunction_1  = Can not simplify transfer function of sample dimension
\u201c{0}\u201d.
 CategoryRangeOverlap_4            = The two categories \u201c{0}\u201d and \u201c{2}\u201d
have overlapping ranges: {1} and {3} respectively.
 CoordinateOutsideDomain_2         = The ({0}, {1}) pixel coordinate is outside iterator domain.
 IllegalCategoryRange_2            = Sample value range {1} for \u201c{0}\u201d category is
illegal.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
index f118ccb..29c230e 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
+++ b/core/sis-raster/src/main/java/org/apache/sis/internal/raster/Resources_fr.properties
@@ -24,7 +24,9 @@
 #   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
 #   U+00A0 NO-BREAK SPACE         before  :
 #
+CanNotEnumerateValuesInRange_1    = Ne peut pas \u00e9num\u00e9rer les valeurs dans la plage
{0}.
 CanNotMapToGridDimensions         = Certaines dimensions de l\u2019enveloppe ne correspondent
pas \u00e0 des dimensions de la grille.
+CanNotSimplifyTransferFunction_1  = Ne peut pas simplifier la fonction de transfert de la
dimension d\u2019\u00e9chantillonnage \u00ab\u202f{0}\u202f\u00bb.
 CategoryRangeOverlap_4            = Les deux cat\u00e9gories \u00ab\u202f{0}\u202f\u00bb
et \u00ab\u202f{2}\u202f\u00bb ont des plages de valeurs qui se chevauchent\u2008: {1} et
{3} respectivement.
 CoordinateOutsideDomain_2         = La coordonn\u00e9e pixel ({0}, {1}) est en dehors du
domaine de l\u2019it\u00e9rateur.
 IllegalCategoryRange_2            = La plage de valeurs {1} pour la cat\u00e9gorie \u00ab\u202f{0}\u202f\u00bb
est ill\u00e9gale.
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
b/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
new file mode 100644
index 0000000..bc3bf75
--- /dev/null
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/SampleDimensionTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.Set;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.referencing.operation.transform.TransferFunction;
+import org.apache.sis.measure.Units;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link SampleDimension}.
+ *
+ * @author  Martin Desruisseaux (IRD, Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+public final strictfp class SampleDimensionTest extends TestCase {
+    /**
+     * Tests the creation of a sample dimensions using builder, then verifies the dimension
properties.
+     */
+    @Test
+    public void testBuilder() {
+        final int    lower  = 10;
+        final int    upper  = 200;
+        final double scale  = 0.1;
+        final double offset = 5.0;
+        final SampleDimension test = new SampleDimension.Builder()
+                .addQualitative(null,      0)           // Default to "No data" name, potentially
locale.
+                .addQualitative("Clouds",  1)
+                .addQualitative("Lands", 255)
+                .addQuantitative("Temperature", lower, upper, scale, offset, Units.CELSIUS)
+                .build();
+
+        final Set<Number> nodataValues = test.getNoDataValues();
+        assertArrayEquals(new Integer[] {0, 1, 255}, nodataValues.toArray());
+
+        final TransferFunction tr = test.getTransferFunctionFormula().get();
+        assertFalse ("identity",  test.getTransferFunction().get().isIdentity());
+        assertFalse ("identity",  tr.getTransform().isIdentity());
+        assertEquals("scale",     scale,  tr.getScale(),  STRICT);
+        assertEquals("offset",    offset, tr.getOffset(), STRICT);
+
+        NumberRange<?> range = test.getSampleRange().get();
+        assertEquals("minimum",   0,   range.getMinDouble(), STRICT);
+        assertEquals("maximum",   255, range.getMaxDouble(), STRICT);
+
+        range = test.getMeasurementRange().get();
+        assertEquals("minimum", lower*scale+offset, range.getMinDouble(true),  CategoryTest.EPS);
+        assertEquals("maximum", upper*scale+offset, range.getMaxDouble(false), CategoryTest.EPS);
+
+        assertEquals("units", Units.CELSIUS, test.getUnits().get());
+    }
+}
diff --git a/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
b/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
index 7c36d0a..5b90307 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/test/suite/RasterTestSuite.java
@@ -35,7 +35,8 @@ import org.junit.BeforeClass;
     org.apache.sis.coverage.grid.GridExtentTest.class,
     org.apache.sis.coverage.grid.GridGeometryTest.class,
     org.apache.sis.coverage.CategoryTest.class,
-    org.apache.sis.coverage.CategoryListTest.class
+    org.apache.sis.coverage.CategoryListTest.class,
+    org.apache.sis.coverage.SampleDimensionTest.class
 })
 public final strictfp class RasterTestSuite extends TestSuite {
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
index 595e1a9..59d03c5 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/MeasurementRange.java
@@ -36,10 +36,18 @@ import org.apache.sis.util.resources.Errors;
  *       Usage of {@code MeasurementRange} with integer types is possible, but no convenience
  *       method is provided for integers because they are usually not representative of the
  *       nature of physical measurements.</li>
+ *   <li>{@link #unit()} for getting the unit of measurement associated to this range.</li>
  *   <li>{@link #convertTo(Unit)} for converting the unit of measurement.</li>
  *   <li>{@link #castTo(Class)} for casting the range values to an other type.</li>
  * </ul>
  *
+ * <div class="section">Null unit of measurement</div>
+ * The unit of measurement should not be null, otherwise a {@link NumberRange} should be
used
+ * instead than {@code MeasurementRange}. Nevertheless this class is tolerant to {@code null}
+ * units in order to support situations where a unit of measurement <em>should</em>
be specified,
+ * but for some reason is unavailable. If the unit of measurement become known at a later
stage,
+ * it can be specified by a call to {@link #convertTo(Unit)}.
+ *
  * <div class="section">Immutability and thread safety</div>
  * This class is immutable and thus inherently thread-safe.
  * Subclasses may or may not be immutable, at implementation choice. But implementors are
@@ -227,6 +235,16 @@ public class MeasurementRange<E extends Number & Comparable<?
super E>> extends
 
     /**
      * Returns the unit of measurement, or {@code null} if unknown.
+     * In principle the unit should never be null, otherwise a {@link NumberRange} should
have been used
+     * instead than {@code MeasurementRange}. Nevertheless this method may return {@code
null} if a unit
+     * <em>should</em> exist but for some reason is unavailable.
+     *
+     * <div class="note"><b>Example:</b>
+     * ISO 19115-1 {@code SampleDimension} specifies that its
+     * {@linkplain org.opengis.metadata.content.SampleDimension#getUnits() unit} property
is mandatory if the
+     * {@linkplain org.opengis.metadata.content.SampleDimension#getMinValue() minimum value}
or
+     * {@linkplain org.opengis.metadata.content.SampleDimension#getMaxValue() maximum value}
are provided.
+     * Nevertheless it happens sometime that this information is missing in metadata.</div>
      *
      * @return the unit of measurement, or {@code null}.
      */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
index 75ae9d7..bfe06f5 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/Range.java
@@ -80,7 +80,7 @@ import org.apache.sis.util.Numbers;
  * @author  Joe White
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Jody Garnett (for parameterized type inspiration)
- * @version 0.8
+ * @version 1.0
  *
  * @param <E>  the type of range elements, typically a {@link Number} subclass or {@link
java.util.Date}.
  *
@@ -287,6 +287,23 @@ public class Range<E extends Comparable<? super E>> implements
CheckedContainer<
     }
 
     /**
+     * Returns {@code true} if this range is both left-bounded and right-bounded.
+     * A {@code true} return value guarantees that:
+     *
+     * <ol>
+     *   <li>both {@link #getMinValue()} and {@link #getMaxValue()} will return non-null
values;</li>
+     *   <li>if minimum and maximum values are numbers, then those numbers are finite.</li>
+     * </ol>
+     *
+     * @return whether this range is left- and right-bounded.
+     *
+     * @since 1.0
+     */
+    public boolean isBounded() {
+        return minValue != null && maxValue != null;
+    }
+
+    /**
      * Returns {@code true} if this range contains the given value. A range never contains
the
      * {@code null} value. This is consistent with the <a href="#skip-navbar_top">class
javadoc</a>
      * stating that null {@linkplain #getMinValue() minimum} or {@linkplain #getMaxValue()
maximum}


Mime
View raw message