sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: More verbose implementation of SampleDimension.toString().
Date Thu, 06 Dec 2018 01:01:13 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 420aacca38d205a0c203b475086fcfddcbbef33f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Dec 5 17:55:26 2018 +0100

    More verbose implementation of SampleDimension.toString().
---
 .../java/org/apache/sis/coverage/CategoryList.java |  62 +++++--
 .../org/apache/sis/coverage/SampleDimension.java   |  87 +++++++---
 .../org/apache/sis/coverage/SampleRangeFormat.java | 183 +++++++++++++++++++++
 .../java/org/apache/sis/measure/RangeFormat.java   |  24 ++-
 .../org/apache/sis/util/resources/Vocabulary.java  |  10 ++
 .../sis/util/resources/Vocabulary.properties       |   2 +
 .../sis/util/resources/Vocabulary_fr.properties    |   2 +
 7 files changed, 333 insertions(+), 37 deletions(-)

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 1fcc080..944341c 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
@@ -19,8 +19,7 @@ package org.apache.sis.coverage;
 import java.util.Arrays;
 import java.util.AbstractList;
 import java.io.Serializable;
-import java.io.IOException;
-import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
 import org.opengis.geometry.DirectPosition;
 import org.opengis.referencing.operation.Matrix;
 import org.opengis.referencing.operation.MathTransform1D;
@@ -30,6 +29,7 @@ import org.apache.sis.io.wkt.UnformattableObjectException;
 import org.apache.sis.geometry.GeneralDirectPosition;
 import org.apache.sis.internal.raster.Resources;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.measure.NumberRange;
 
 import static java.lang.Double.isNaN;
@@ -56,6 +56,11 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     private static final long serialVersionUID = 2647846361059903365L;
 
     /**
+     * An empty list of categories.
+     */
+    static final CategoryList EMPTY = new CategoryList();
+
+    /**
      * The result of converting sample values to real values, never {@code null}.
      */
     final CategoryList converted;
@@ -110,9 +115,23 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     private transient Category last;
 
     /**
+     * The constructor for the {@link #EMPTY} constant.
+     */
+    private CategoryList() {
+        converted     = this;
+        range         = null;
+        minimums      = ArraysExt.EMPTY_DOUBLE;
+        categories    = new Category[0];
+        main          = null;
+        extrapolation = null;
+    }
+
+    /**
      * Constructs a category list using the specified array of categories.
+     * The {@code categories} array should contain at least one element.
      *
-     * @param  categories  the list of categories. May be empty, but can not be null. This
array is not cloned.
+     * @param  categories  the list of categories. May be empty, but can not be null.
+     *                     This array is not cloned and is modified in-place.
      * @param  inverse     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.
@@ -198,15 +217,18 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     }
 
     /**
-     * Resets the {@link #last} field to a non-null value after deserialization.
+     * Computes transient fields and potentially returns a shared instance.
      *
-     * @param  in  the input stream from which to deserialize a category list.
-     * @throws IOException if an I/O error occurred while reading or if the stream contains
invalid data.
-     * @throws ClassNotFoundException if the class serialized on the stream is not on the
classpath.
+     * @return the object to use after deserialization.
+     * @throws ObjectStreamException if the serialized object contains invalid data.
      */
-    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException
{
-        in.defaultReadObject();
-        last = (main != null || categories.length == 0) ? main : categories[0];
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    private Object readResolve() throws ObjectStreamException {
+        if (categories.length == 0) {
+            return EMPTY;
+        }
+        last = (main != null) ? main : categories[0];
+        return this;
     }
 
     /**
@@ -233,6 +255,26 @@ final class CategoryList extends AbstractList<Category> implements
MathTransform
     }
 
     /**
+     * Returns the <cite>transfer function</cite> from sample values to real
values, including conversion
+     * of "no data" value to NaN. If there is no quantitative categories, returns {@code
null}.
+     *
+     * @see SampleDimension#getTransferFunction()
+     */
+    final MathTransform1D getTransferFunction() {
+        MathTransform1D tr = null;
+        if (hasQuantitative()) {
+            tr = categories[0].transferFunction;
+            for (int i=1; i<categories.length; i++) {
+                if (!tr.equals(categories[i].transferFunction)) {
+                    tr = this;
+                    break;
+                }
+            }
+        }
+        return tr;
+    }
+
+    /**
      * Performs a bi-linear search of the specified value. This method is similar to
      * {@link Arrays#binarySearch(double[],double)} except that it can differentiate
      * the various NaN 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 8b0bbbc..8b361d9 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
@@ -22,7 +22,11 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.HashSet;
 import java.util.Optional;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Locale;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
 import javax.measure.Unit;
 import org.opengis.util.InternationalString;
@@ -34,7 +38,6 @@ import org.apache.sis.measure.NumberRange;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.iso.Types;
-import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 
 
@@ -70,7 +73,7 @@ import org.apache.sis.util.Numbers;
  * @since 1.0
  * @module
  */
-public final class SampleDimension implements Serializable {
+public class SampleDimension implements Serializable {
     /**
      * Serial number for inter-operability with different versions.
      */
@@ -92,36 +95,48 @@ public final class SampleDimension implements Serializable {
 
     /**
      * The transform from samples to real values. May be {@code null} if this sample dimension
-     * does not defines any transform (which is not the same that defining an identity transform).
+     * does not define any transform (which is not the same that defining an identity transform).
      *
      * @see #getTransferFunction()
      */
-    private final MathTransform1D transferFunction;
+    private transient MathTransform1D transferFunction;
 
     /**
      * Creates a sample dimension with the specified properties.
      *
      * @param name        the sample dimension title or description, or {@code null} for
default.
-     * @param categories  the list of categories. This list is not cloned and may be modified
in-place.
+     * @param categories  the list of categories.
      */
-    SampleDimension(InternationalString name, final Category[] categories) {
-        if (name == null) {
-            name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
+    SampleDimension(InternationalString name, final Collection<? extends Category>
categories) {
+        ArgumentChecks.ensureNonNull("categories", categories);
+        final CategoryList list;
+        if (categories.isEmpty()) {
+            list = CategoryList.EMPTY;
+        } else {
+            list = new CategoryList(categories.toArray(new Category[categories.size()]),
null);
         }
-        final CategoryList list = new CategoryList(categories, null);
-        this.name       = name;
-        this.categories = list;
-        MathTransform1D tr = null;
-        if (list.hasQuantitative()) {
-            tr = categories[0].transferFunction;
-            for (int i=1; i<categories.length; i++) {
-                if (!tr.equals(categories[i].transferFunction)) {
-                    tr = list;
-                    break;
-                }
+        if (name == null) {
+            if (list.main != null) {
+                name = list.main.name;
+            } else {
+                name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled);
             }
         }
-        transferFunction = tr;
+        this.name        = name;
+        this.categories  = list;
+        transferFunction = list.getTransferFunction();
+    }
+
+    /**
+     * Computes transient fields after deserialization.
+     *
+     * @param  in  the input stream from which to deserialize a sample dimension.
+     * @throws IOException if an I/O error occurred while reading or if the stream contains
invalid data.
+     * @throws ClassNotFoundException if the class serialized on the stream is not on the
classpath.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException
{
+        in.defaultReadObject();
+        transferFunction = categories.getTransferFunction();
     }
 
     /**
@@ -283,6 +298,32 @@ public final class SampleDimension implements Serializable {
     }
 
     /**
+     * Returns a hash value for this sample dimension.
+     */
+    @Override
+    public int hashCode() {
+        return categories.hashCode() + 31*name.hashCode();
+    }
+
+    /**
+     * Compares the specified object with this sample dimension for equality.
+     *
+     * @param  object  the object to compare with.
+     * @return {@code true} if the given object is equals to this sample dimension.
+     */
+    @Override
+    public boolean equals(final Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof SampleDimension) {
+            final SampleDimension that = (SampleDimension) object;
+            return name.equals(that.name) && categories.equals(that.categories);
+        }
+        return false;
+    }
+
+    /**
      * Returns a string representation of this sample dimension.
      * This string is for debugging purpose only and may change in future version.
      *
@@ -290,7 +331,7 @@ public final class SampleDimension implements Serializable {
      */
     @Override
     public String toString() {
-        return Classes.getShortClassName(this) + "[“" + name + "”]";
+        return new SampleRangeFormat(Locale.getDefault()).format(name, categories);
     }
 
 
@@ -597,9 +638,7 @@ public final class SampleDimension implements Serializable {
          * @return the sample dimension.
          */
         public SampleDimension build() {
-            return new SampleDimension(
-                    Types.toInternationalString(dimensionName),
-                    categories.toArray(new Category[categories.size()]));
+            return new SampleDimension(Types.toInternationalString(dimensionName), categories);
         }
     }
 }
diff --git a/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
new file mode 100644
index 0000000..c6dd944
--- /dev/null
+++ b/core/sis-raster/src/main/java/org/apache/sis/coverage/SampleRangeFormat.java
@@ -0,0 +1,183 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.text.NumberFormat;
+import java.io.IOException;
+import org.opengis.util.InternationalString;
+import org.apache.sis.io.TableAppender;
+import org.apache.sis.measure.Range;
+import org.apache.sis.measure.RangeFormat;
+import org.apache.sis.internal.util.Numerics;
+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
+ * @since   1.0
+ * @module
+ */
+@SuppressWarnings({"CloneableClassWithoutClone", "serial"})         // Not intended to be
cloned or serialized.
+final class SampleRangeFormat extends RangeFormat {
+    /**
+     * Maximum value for {@link #ndigits}. This is the number of
+     * significant digits to allow when formatting real values.
+     */
+    private static final int MAX_DIGITS = 6;
+
+    /**
+     * Number of significant digits used for formatting real values.
+     */
+    private int ndigits;
+
+    /**
+     * The localize resources for table header. Words will be "Values", "Measures" and "Name".
+     */
+    private final Vocabulary words;
+
+    /**
+     * Creates a new format for the given locale.
+     *
+     * @param locale   the locale for table header, category names and number format.
+     */
+    SampleRangeFormat(final Locale locale) {
+        super(locale);
+        words = Vocabulary.getResources(locale);
+    }
+
+    /**
+     * Computes the smallest number of fraction digits necessary to resolve all quantitative
values.
+     * This method assumes that real values in the range {@code Category.converted.range}
are stored
+     * as integer sample values in the range {@code Category.range}.
+     *
+     * @return {@code true} if at least one quantitative category has been found.
+     */
+    private boolean prepare(final List<Category> categories) {
+        ndigits = 0;
+        boolean hasQuantitative = false;
+        for (final Category category : categories) {
+            final Category converted = category.converted;
+            final double increment = (converted.maximum - converted.minimum)
+                                   / ( category.maximum -  category.minimum);
+            if (!Double.isNaN(increment)) {
+                hasQuantitative = true;
+                final int n = -Numerics.toExp10(Math.getExponent(increment));
+                if (n > ndigits) {
+                    ndigits = n;
+                    if (n >= MAX_DIGITS) {
+                        ndigits = MAX_DIGITS;
+                        break;
+                    }
+                }
+            }
+        }
+        return hasQuantitative;
+    }
+
+    /**
+     * Formats a sample value or a range of sample value.
+     * The value should have been fetched by {@link Category#getRangeLabel()}.
+     */
+    private String formatSample(final Object value) {
+        if (value instanceof Number) {
+            return elementFormat.format(value).concat(" ");
+        } else if (value instanceof Range<?>) {
+            return format(value);
+        } else {
+            return String.valueOf(value);
+        }
+    }
+
+    /**
+     * Formats a range of measurements. There is usually only zero or one range of measurement
per {@link SampleDimension},
+     * but {@code SampleRangeFormat} is not restricted to that limit. The number of fraction
digits to use should have been
+     * computed by {@link #prepare(List)} before to call this method.
+     */
+    private String formatMeasure(final Range<?> range) {
+        if (range == null) {
+            return "";
+        }
+        final NumberFormat nf = (NumberFormat) elementFormat;
+        final int min = nf.getMinimumFractionDigits();
+        final int max = nf.getMaximumFractionDigits();
+        try {
+            nf.setMinimumFractionDigits(ndigits);
+            nf.setMaximumFractionDigits(ndigits);
+            return format(range);
+        } finally {
+            nf.setMinimumFractionDigits(min);
+            nf.setMaximumFractionDigits(max);
+        }
+    }
+
+    /**
+     * Returns a string representation of the given list of categories.
+     *
+     * @param title       caption for the table.
+     * @param categories  the list of categories to format.
+     */
+    final String format(final InternationalString title, final CategoryList categories) {
+        final StringBuilder buffer = new StringBuilder(800);
+        try {
+            format(title, categories, buffer);
+        } catch (IOException e) {
+            throw new AssertionError(e);    // Should never happen since we write to a StringBuilder.
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Formats a string representation of the given list of categories.
+     *
+     * @param title       caption for the table.
+     * @param categories  the list of categories to format.
+     * @param out         where to write the category table.
+     */
+    void format(final InternationalString title, final CategoryList categories, final Appendable
out) throws IOException {
+        final String lineSeparator = System.lineSeparator();
+        out.append(title.toString(getLocale())).append(lineSeparator);
+        final TableAppender table  = new TableAppender(out);
+        final boolean hasQuantitative = prepare(categories);
+        table.nextLine('═');
+        table.setCellAlignment(TableAppender.ALIGN_CENTER);
+        table.append(words.getString(Vocabulary.Keys.Values)).nextColumn();
+        if (hasQuantitative) {
+            table.append(words.getString(Vocabulary.Keys.Measures)).nextColumn();
+        }
+        table.append(words.getString(Vocabulary.Keys.Name)).nextLine();
+        table.appendHorizontalSeparator();
+        for (final Category category : categories) {
+            table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+            table.append(formatSample(category.getRangeLabel()));
+            table.nextColumn();
+            if (hasQuantitative) {
+                table.append(formatMeasure(category.converted.range));
+                table.nextColumn();
+            }
+            table.setCellAlignment(TableAppender.ALIGN_LEFT);
+            table.append(category.name.toString(getLocale()));
+            table.nextLine();
+        }
+        table.nextLine('═');
+        table.flush();
+    }
+}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java b/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
index 302fd4c..9fde2b9 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/RangeFormat.java
@@ -34,6 +34,8 @@ import java.text.ParsePosition;
 import java.security.AccessController;
 import javax.measure.Unit;
 import org.apache.sis.util.Numbers;
+import org.apache.sis.util.Localized;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.UnconvertibleObjectException;
 import org.apache.sis.internal.util.LocalizedParseException;
@@ -91,7 +93,7 @@ import org.apache.sis.internal.util.FinalFieldSetter;
  * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see Range#toString()
  * @see <a href="http://en.wikipedia.org/wiki/ISO_31-11">Wikipedia: ISO 31-11</a>
@@ -99,7 +101,7 @@ import org.apache.sis.internal.util.FinalFieldSetter;
  * @since 0.3
  * @module
  */
-public class RangeFormat extends Format {
+public class RangeFormat extends Format implements Localized {
     /**
      * For cross-version compatibility.
      */
@@ -345,6 +347,8 @@ public class RangeFormat extends Format {
      * @throws IllegalArgumentException if the given type is not recognized by this constructor.
      */
     public RangeFormat(final Locale locale, final Class<?> elementType) throws IllegalArgumentException
{
+        ArgumentChecks.ensureNonNull("locale",      locale);
+        ArgumentChecks.ensureNonNull("elementType", elementType);
         this.locale      = locale;
         this.elementType = elementType;
         if (Angle.class.isAssignableFrom(elementType)) {
@@ -393,6 +397,19 @@ public class RangeFormat extends Format {
     }
 
     /**
+     * Returns this formatter locale. This is the locale specified at construction time if
any,
+     * or the {@linkplain Locale#getDefault() default locale} at construction time otherwise.
+     *
+     * @return this formatter locale (never {@code null}).
+     *
+     * @since 1.0
+     */
+    @Override
+    public Locale getLocale() {
+        return locale;
+    }
+
+    /**
      * Returns the pattern used by {@link #elementFormat} for formatting the minimum and
      * maximum values. If the element format does not use pattern, returns {@code null}.
      *
@@ -459,7 +476,7 @@ public class RangeFormat extends Format {
      * formatting time. The alternate form expresses open intervals like {@code ]a…b[}
      * instead of {@code (a…b)}.
      *
-     * <p>This flag as no effect on parsing, since the parser accepts both forms.</p>
+     * <p>This flag has no effect on parsing, since the parser accepts both forms.</p>
      *
      * @return {@code true} for using the alternate format instead of the default format.
      */
@@ -469,6 +486,7 @@ public class RangeFormat extends Format {
 
     /**
      * Sets whether this {@code RangeFormat} shall use the alternate form at formatting time.
+     * The alternate form expresses open intervals like {@code ]a…b[} instead of {@code
(a…b)}.
      *
      * @param alternateForm {@code true} for using the alternate format, or {@code false}
for using the default format.
      */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
index 8a279b3..a42b856 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
@@ -502,6 +502,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short MeanValue = 68;
 
         /**
+         * Measures
+         */
+        public static final short Measures = 157;
+
+        /**
          * Methods
          */
         public static final short Methods = 69;
@@ -797,6 +802,11 @@ public final class Vocabulary extends IndexedResourceBundle {
         public static final short ValueDomain = 112;
 
         /**
+         * Values
+         */
+        public static final short Values = 158;
+
+        /**
          * Variables
          */
         public static final short Variables = 113;
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
index 60e7f2c..b2c7a9e 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
@@ -104,6 +104,7 @@ Mapping                 = Mapping
 MaximumValue            = Maximum value
 MeanValue               = Mean value
 MinimumValue            = Minimum value
+Measures                = Measures
 Methods                 = Methods
 ModifiedJulian          = Modified Julian
 Multiplicity            = Multiplicity
@@ -161,6 +162,7 @@ UnavailableContent      = Unavailable content.
 Units                   = Units
 UserHome                = User home directory
 Value                   = Value
+Values                  = Values
 ValueDomain             = Value domain
 Variables               = Variables
 Version_2               = {0} version {1}
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
index b44256c..47dffd8 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -111,6 +111,7 @@ Mapping                 = Cartographie
 MaximumValue            = Valeur maximale
 MeanValue               = Valeur moyenne
 MinimumValue            = Valeur minimale
+Measures                = Mesures
 Methods                 = M\u00e9thodes
 ModifiedJulian          = Julien modifi\u00e9
 Multiplicity            = Multiplicit\u00e9
@@ -168,6 +169,7 @@ UnavailableContent      = Contenu non-disponible.
 Units                   = Unit\u00e9s
 UserHome                = R\u00e9pertoire de l\u2019utilisateur
 Value                   = Valeur
+Values                  = Valeurs
 ValueDomain             = Domaine des valeurs
 Variables               = Variables
 Version_2               = {0} version {1}


Mime
View raw message