sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 03/04: Replace the 'padValues' argument by a 'toNaN' mapping function in Category constructor. This give more control, for example for selecting a more efficient background value.
Date Sat, 08 Dec 2018 18:01:52 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 7462355e0069a76164ea0c94800e983fef7f42b6
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Sat Dec 8 16:14:52 2018 +0100

    Replace the 'padValues' argument by a 'toNaN' mapping function in Category constructor.
    This give more control, for example for selecting a more efficient background value.
---
 .../java/org/apache/sis/coverage/Category.java     | 53 ++++---------
 .../org/apache/sis/coverage/ConvertedRange.java    |  2 +-
 .../org/apache/sis/coverage/SampleDimension.java   | 19 ++---
 .../main/java/org/apache/sis/coverage/ToNaN.java   | 88 ++++++++++++++++++++++
 .../org/apache/sis/coverage/CategoryListTest.java  | 24 +++---
 .../java/org/apache/sis/coverage/CategoryTest.java | 15 ++--
 6 files changed, 129 insertions(+), 72 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 b10da34..f7c16a5 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
@@ -16,10 +16,10 @@
  */
 package org.apache.sis.coverage;
 
-import java.util.Set;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Comparator;
+import java.util.function.DoubleToIntFunction;
 import java.io.Serializable;
 import javax.measure.Unit;
 import org.opengis.util.InternationalString;
@@ -190,22 +190,23 @@ public class Category implements Serializable {
      * Constructs a qualitative or quantitative category. This constructor is accessible
for sub-classing.
      * For other usages, {@link SampleDimension.Builder} should be used instead.
      *
-     * @param  name       the category name (mandatory).
-     * @param  samples    the minimum and maximum sample values (mandatory).
-     * @param  toUnits    the conversion from sample values to real values,
-     *                    or {@code null} for constructing a qualitative category.
-     * @param  units      the units of measurement, or {@code null} if not applicable.
-     *                    This is the target units after conversion by {@code toUnits}.
-     * @param  padValues  an initially empty set to be filled by this constructor for avoiding
pad value collisions.
-     *                    The same set shall be given to all {@code Category} created for
the same sample dimension.
-     *                    Content can be discarded after all categories have been created.
+     * @param  name     the category name (mandatory).
+     * @param  samples  the minimum and maximum sample values (mandatory).
+     * @param  toUnits  the conversion from sample values to real values,
+     *                  or {@code null} for constructing a qualitative category.
+     * @param  units    the units of measurement, or {@code null} if not applicable.
+     *                  This is the target units after conversion by {@code toUnits}.
+     * @param  toNaN    mapping from sample values to ordinal values to be supplied to {@link
MathFunctions#toNanFloat(int)}.
+     *                  That mapping is used only if {@code toUnits} is {@code null}. That
mapping is responsible to ensure that
+     *                  there is no ordinal value collision between different categories
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.
      */
     protected Category(final CharSequence name, NumberRange<?> samples, final MathTransform1D
toUnits, final Unit<?> units,
-             final Set<Integer> padValues)
+             final DoubleToIntFunction toNaN)
     {
         ArgumentChecks.ensureNonEmpty("name", name);
         ArgumentChecks.ensureNonNull("samples", samples);
-        ArgumentChecks.ensureNonNull("padValues", padValues);
         this.name    = Types.toInternationalString(name);
         this.minimum = samples.getMinDouble(true);
         this.maximum = samples.getMaxDouble(true);
@@ -234,32 +235,8 @@ public class Category implements Serializable {
                 }
                 toSamples = toUnits.inverse();
             } else {
-                /*
-                 * For qualitative category, we need an ordinal in the [MIN_NAN_ORDINAL …
MAX_NAN_ORDINAL] range.
-                 * This range is quite large (a few million of values) so using the sample
directly usually work.
-                 * If it does not work, we will use an arbitrary value in that range.
-                 */
-                int ordinal = Math.round((float) minimum);
-                if (ordinal > MathFunctions.MAX_NAN_ORDINAL) {
-                    ordinal = (MathFunctions.MAX_NAN_ORDINAL + 1) / 2;
-                } else if (ordinal < MathFunctions.MIN_NAN_ORDINAL) {
-                    ordinal = MathFunctions.MIN_NAN_ORDINAL / 2;
-                }
-search:         if (!padValues.add(ordinal)) {
-                    /*
-                     * Following algorithms are inefficient, but those loops should be rarely
needed.
-                     * They are executed only if many qualitative sample values are outside
the range
-                     * of ordinal NaN values. The range allows a few million of values.
-                     */
-                    if (ordinal >= 0) {
-                        do if (padValues.add(++ordinal)) break search;
-                        while (ordinal < MathFunctions.MAX_NAN_ORDINAL);
-                    } else {
-                        do if (padValues.add(--ordinal)) break search;
-                        while (ordinal > MathFunctions.MIN_NAN_ORDINAL);
-                    }
-                    throw new IllegalStateException(Resources.format(Resources.Keys.TooManyQualitatives));
-                }
+                ArgumentChecks.ensureNonNull("toNaN", toNaN);
+                final int ordinal = toNaN.applyAsInt(minimum);
                 /*
                  * For qualitative category, the transfer function maps to NaN while the
inverse function maps back
                  * to some value in the [minimum … maximum] range. We chose the value closest
to positive zero.
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedRange.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedRange.java
index ce3c090..a0f2375 100644
--- a/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedRange.java
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/ConvertedRange.java
@@ -24,7 +24,7 @@ import org.apache.sis.measure.MeasurementRange;
 
 /**
  * Range of real values computed from the range of the sample values.
- * The {@link Category#transferFunction} conversion is used by the caller for computing the
inclusive and exclusive
+ * The {@link Category#toConverse} conversion is used by the caller for computing the inclusive
and exclusive
  * minimum and maximum values of this range. We compute both the inclusive and exclusive
values because we can not
  * rely on the default implementation, which looks for the nearest representable number.
For example if the range
  * of sample values is 0 to 10 exclusive (or 0 to 9 inclusive) and the scale is 2, then the
range of real values
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 55be3f6..d7616b4 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
@@ -20,7 +20,6 @@ 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.Collection;
 import java.util.Collections;
@@ -510,7 +509,7 @@ public class SampleDimension implements Serializable {
          * The ordinal NaN values used for this sample dimension.
          * The {@link Category} constructor uses this set for avoiding collisions.
          */
-        private final Set<Integer> padValues;
+        private final ToNaN toNaN;
 
         /**
          * Creates an initially empty builder for a sample dimension.
@@ -518,7 +517,7 @@ public class SampleDimension implements Serializable {
          */
         public Builder() {
             categories = new ArrayList<>();
-            padValues  = new HashSet<>();
+            toNaN      = new ToNaN();
         }
 
         /**
@@ -568,8 +567,9 @@ public class SampleDimension implements Serializable {
                 name = Vocabulary.formatInternational(Vocabulary.Keys.FillValue);
             }
             final NumberRange<?> samples = range(sample);
-            categories.add(new Category(name, samples, null, null, padValues));
             background = samples.getMinValue();
+            toNaN.background = background.doubleValue();
+            categories.add(new Category(name, samples, null, null, toNaN));
             return this;
         }
 
@@ -703,7 +703,7 @@ public class SampleDimension implements Serializable {
             if (name == null) {
                 name = Vocabulary.formatInternational(Vocabulary.Keys.Nodata);
             }
-            categories.add(new Category(name, samples, null, null, padValues));
+            categories.add(new Category(name, samples, null, null, toNaN));
             return this;
         }
 
@@ -811,10 +811,7 @@ public class SampleDimension implements Serializable {
          */
         public Builder addQuantitative(CharSequence name, NumberRange<?> samples, MathTransform1D
toUnits, Unit<?> units) {
             ArgumentChecks.ensureNonNull("toUnits", toUnits);
-            if (units != null && toUnits.isIdentity() && samples != null
&& !(samples instanceof MeasurementRange<?>)) {
-                samples = new MeasurementRange<>(samples, units);
-            }
-            categories.add(new Category(name, samples, toUnits, units, padValues));
+            categories.add(new Category(name, samples, toUnits, units, toNaN));
             return this;
         }
 
@@ -842,11 +839,11 @@ public class SampleDimension implements Serializable {
          */
         public SampleDimension build() {
             InternationalString name = Types.toInternationalString(dimensionName);
-dfname:     if (name == null) {
+defName:    if (name == null) {
                 for (final Category category : categories) {
                     if (category.isQuantitative()) {
                         name = category.name;
-                        break dfname;
+                        break defName;
                     }
                 }
                 name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java b/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
new file mode 100644
index 0000000..50d040a
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/ToNaN.java
@@ -0,0 +1,88 @@
+/*
+ * 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.HashSet;
+import java.util.function.DoubleToIntFunction;
+import org.apache.sis.math.MathFunctions;
+import org.apache.sis.internal.raster.Resources;
+
+
+/**
+ * Keep trace of allocated {@link Float#NaN} ordinal values for avoiding range collisions
when building categories.
+ * This is a temporary object used only at {@link SampleDimension} construction time for
producing values suitable
+ * to {@link MathFunctions#toNanFloat(int)}. Instances of this class are given for {@code
toNaN} argument in the
+ * {@link Category} constructor.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.0
+ * @since   1.0
+ * @module
+ */
+@SuppressWarnings({"CloneableClassWithoutClone", "serial"})         // Not intended to be
cloned or serialized.
+final class ToNaN extends HashSet<Integer> implements DoubleToIntFunction {
+    /**
+     * The value which should be assigned ordinal 0 if that ordinal value is available.
+     * For performance reason, the background value should be assigned ordinal 0 when possible.
+     */
+    double background;
+
+    /**
+     * To be constructed only from this package.
+     */
+    ToNaN() {
+        background = Double.NaN;
+    }
+
+    /**
+     * Returns a NaN ordinal value for the given sample value.
+     * The returned value can be given to {@link MathFunctions#toNanFloat(int)}.
+     */
+    @Override
+    public int applyAsInt(final double value) {
+        if (value == background && add(0)) {
+            return 0;
+        }
+        /*
+         * For qualitative category, we need an ordinal in the [MIN_NAN_ORDINAL … MAX_NAN_ORDINAL]
range.
+         * This range is quite large (a few million of values) so using the sample directly
usually work.
+         * If it does not work, we will use an arbitrary value in that range.
+         */
+        int ordinal = Math.round((float) value);
+        if (ordinal > MathFunctions.MAX_NAN_ORDINAL) {
+            ordinal = (MathFunctions.MAX_NAN_ORDINAL + 1) / 2;
+        } else if (ordinal < MathFunctions.MIN_NAN_ORDINAL) {
+            ordinal = MathFunctions.MIN_NAN_ORDINAL / 2;
+        }
+search: if (!add(ordinal)) {
+            /*
+             * Following algorithms are inefficient, but those loops should be rarely needed.
+             * They are executed only if many qualitative sample values are outside the range
+             * of ordinal NaN values. The range allows a few million of values.
+             */
+            if (ordinal >= 0) {
+                do if (add(++ordinal)) break search;
+                while (ordinal < MathFunctions.MAX_NAN_ORDINAL);
+            } else {
+                do if (add(--ordinal)) break search;
+                while (ordinal > MathFunctions.MIN_NAN_ORDINAL);
+            }
+            throw new IllegalStateException(Resources.format(Resources.Keys.TooManyQualitatives));
+        }
+        return ordinal;
+    }
+}
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
index 8a4d5fa..211a53a 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryListTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.coverage;
 
-import java.util.Set;
-import java.util.HashSet;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
@@ -75,12 +73,12 @@ public final strictfp class CategoryListTest extends TestCase {
      */
     @Test
     public void testArgumentChecks() {
-        final Set<Integer> padValues = new HashSet<>();
+        final ToNaN toNaN = new ToNaN();
         Category[] categories = {
-            new Category("No data", NumberRange.create( 0, true,  0, true), null, null, padValues),
-            new Category("Land",    NumberRange.create(10, true, 10, true), null, null, padValues),
-            new Category("Clouds",  NumberRange.create( 2, true,  2, true), null, null, padValues),
-            new Category("Again",   NumberRange.create(10, true, 10, true), null, null, padValues)
      // Range overlaps.
+            new Category("No data", NumberRange.create( 0, true,  0, true), null, null, toNaN),
+            new Category("Land",    NumberRange.create(10, true, 10, true), null, null, toNaN),
+            new Category("Clouds",  NumberRange.create( 2, true,  2, true), null, null, toNaN),
+            new Category("Again",   NumberRange.create(10, true, 10, true), null, null, toNaN)
      // Range overlaps.
         };
         try {
             assertNotConverted(new CategoryList(categories.clone(), null));
@@ -163,13 +161,13 @@ public final strictfp class CategoryListTest extends TestCase {
      * Creates an array of category for {@link #testSearch()} and {@link #testTransform()}.
      */
     private static Category[] categories() {
-        final Set<Integer> padValues = new HashSet<>();
+        final ToNaN toNaN = new ToNaN();
         return new Category[] {
-            /*[0]*/ new Category("No data",     NumberRange.create(  0, true,   0, true),
null, null, padValues),
-            /*[1]*/ new Category("Land",        NumberRange.create(  7, true,   7, true),
null, null, padValues),
-            /*[2]*/ new Category("Clouds",      NumberRange.create(  3, true,   3, true),
null, null, padValues),
-            /*[3]*/ new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5), null, padValues),
-            /*[4]*/ new Category("Foo",         NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear( -1, 3), null, padValues)
+            /*[0]*/ new Category("No data",     NumberRange.create(  0, true,   0, true),
null, null, toNaN),
+            /*[1]*/ new Category("Land",        NumberRange.create(  7, true,   7, true),
null, null, toNaN),
+            /*[2]*/ new Category("Clouds",      NumberRange.create(  3, true,   3, true),
null, null, toNaN),
+            /*[3]*/ new Category("Temperature", NumberRange.create( 10, true, 100, false),
(MathTransform1D) MathTransforms.linear(0.1, 5), null, toNaN),
+            /*[4]*/ new Category("Foo",         NumberRange.create(100, true, 120, false),
(MathTransform1D) MathTransforms.linear( -1, 3), null, toNaN)
         };
     }
 
diff --git a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
index b0fe445..df2db15 100644
--- a/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
+++ b/core/sis-raster/src/test/java/org/apache/sis/coverage/CategoryTest.java
@@ -16,10 +16,7 @@
  */
 package org.apache.sis.coverage;
 
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.Random;
-import java.util.Set;
 import org.opengis.referencing.operation.MathTransform1D;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.referencing.operation.transform.MathTransforms;
@@ -76,12 +73,12 @@ public final strictfp class CategoryTest extends TestCase {
     @Test
     public void testQualitativeCategory() throws TransformException {
         final Random random = TestUtilities.createRandomNumberGenerator();
-        final Set<Integer> padValues = new HashSet<>();
+        final ToNaN toNaN = new ToNaN();
         for (int pass=0; pass<20; pass++) {
             final int      sample    = random.nextInt(20);
-            final boolean  collision = padValues.contains(sample);
-            final Category category  = new Category("Random", NumberRange.create(sample,
true, sample, true), null, null, padValues);
-            assertTrue("Allocated NaN ordinal", padValues.contains(sample));
+            final boolean  collision = toNaN.contains(sample);
+            final Category category  = new Category("Random", NumberRange.create(sample,
true, sample, true), null, null, toNaN);
+            assertTrue("Allocated NaN ordinal", toNaN.contains(sample));
             /*
              * Verify properties on the category that we created.
              * The sample values are integers in our test.
@@ -151,7 +148,7 @@ public final strictfp class CategoryTest extends TestCase {
             final double  scale = 10*random.nextDouble() + 0.1;         // Must be positive
for this test.
             final double offset = 10*random.nextDouble() - 5.0;
             final Category category = new Category("Random", NumberRange.create(lower, true,
upper, true),
-                    (MathTransform1D) MathTransforms.linear(scale, offset), null, Collections.emptySet());
+                    (MathTransform1D) MathTransforms.linear(scale, offset), null, null);
 
             final Category converse = category.converse;
             assertNotSame    ("converse",           category, converse);
@@ -205,7 +202,7 @@ public final strictfp class CategoryTest extends TestCase {
             final double lower = random.nextDouble() * 5;
             final double upper = random.nextDouble() * 10 + lower;
             final Category category = new Category("Random", NumberRange.create(lower, true,
upper, true),
-                    (MathTransform1D) MathTransforms.identity(1), null, Collections.emptySet());
+                    (MathTransform1D) MathTransforms.identity(1), null, null);
 
             assertSame       ("converse",           category,            category.converse);
             assertEquals     ("name",               "Random",            String.valueOf(category.name));


Mime
View raw message