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 2022/12/13 18:23:32 UTC

[sis] 03/03: Fix an exception when GeoTIFF metadata contains rational numbers.

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 7490457b360472dce2414f7094999687540ba1ba
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Tue Dec 13 19:21:37 2022 +0100

    Fix an exception when GeoTIFF metadata contains rational numbers.
---
 .../org/apache/sis/internal/util/Numerics.java     | 20 ++++++
 .../java/org/apache/sis/storage/geotiff/Type.java  | 76 +++++++++++++---------
 2 files changed, 66 insertions(+), 30 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
index a16dabd699..8886018831 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/util/Numerics.java
@@ -29,6 +29,7 @@ import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.math.Statistics;
+import org.apache.sis.math.Fraction;
 
 import static java.lang.Math.min;
 import static java.lang.Math.max;
@@ -328,6 +329,25 @@ public final class Numerics extends Static {
         return (int) value;
     }
 
+    /**
+     * Returns the given fraction as a {@link Fraction} instance if possible,
+     * or as a {@link Double} approximation otherwise.
+     *
+     * @param  numerator    numerator of the fraction to return.
+     * @param  denominator  denominator of the fraction to return.
+     * @return the fraction as a {@link Fraction} or {@link Double} object.
+     */
+    public static Number fraction(long numerator, long denominator) {
+        final int simplify = Math.min(Long.numberOfTrailingZeros(numerator), Long.numberOfTrailingZeros(denominator));
+        final int num = (int) (numerator   >>= simplify);
+        final int den = (int) (denominator >>= simplify);
+        if (num == numerator && den == denominator) {
+            return new Fraction(num, den).unique();
+        } else {
+            return valueOf(numerator / (double) denominator);
+        }
+    }
+
     /**
      * If the given value is presents in the cache, returns the cached value.
      * Otherwise returns the given value as-is.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
index 8737f7e806..8da57cbcbd 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
@@ -37,7 +37,7 @@ import org.apache.sis.util.resources.Errors;
  * This enumeration rather match the Java primitive type names.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.4
  * @since   0.8
  * @module
  */
@@ -296,21 +296,16 @@ enum Type {
      * </ul>
      */
     RATIONAL(10, (2*Integer.BYTES), false) {
-        private Fraction readFraction(final ChannelDataInput input, final long count) throws IOException {
-            final Fraction value = new Fraction(input.readInt(), input.readInt());
-            for (long i=1; i<count; i++) {
-                ensureSingleton(value.doubleValue(), input.readInt() / (double) input.readInt(), count);
-            }
-            return value;
-        }
-
         @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
             return readFraction(input, count).doubleValue();
         }
 
-        /** Returns the value as a {@link Fraction}. */
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
+            return readFractions(input, count);
+        }
+
         @Override public Object readObject(final ChannelDataInput input, final long count) throws IOException {
-            return readFraction(input, count);
+            return (count == 1) ? readFraction(input, count) : super.readObject(input, count);
         }
     },
 
@@ -322,25 +317,16 @@ enum Type {
      * </ul>
      */
     URATIONAL(5, (2*Integer.BYTES), true) {
-        private Number readFraction(final ChannelDataInput input, final long count) throws IOException {
-            final long n  = input.readUnsignedInt();
-            final long d  = input.readUnsignedInt();
-            final int  ni = (int) n;
-            final int  di = (int) d;
-            final Number value = (ni == n && di == d) ? new Fraction(ni, di) : Double.valueOf(n / (double) d);
-            for (long i=1; i<count; i++) {
-                ensureSingleton(value.doubleValue(), input.readUnsignedInt() / (double) input.readUnsignedInt(), count);
-            }
-            return value;
-        }
-
         @Override public double readDouble(final ChannelDataInput input, final long count) throws IOException {
             return readFraction(input, count).doubleValue();
         }
 
-        /** Returns the value as {@link Faction} if possible or {@link Double} otherwise. */
+        @Override public Object readArray(final ChannelDataInput input, final int count) throws IOException {
+            return readFractions(input, count);
+        }
+
         @Override public Object readObject(final ChannelDataInput input, final long count) throws IOException {
-            return readFraction(input, count);
+            return (count == 1) ? readFraction(input, count) : super.readObject(input, count);
         }
     },
 
@@ -451,10 +437,38 @@ enum Type {
     }
 
     /**
-     * Formats a rational number. This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
+     * Reads the next rational number. This is either a single value or a member of a vector.
+     */
+    private Number nextFraction(final ChannelDataInput input) throws IOException {
+        if (isUnsigned) {
+            return Numerics.fraction(input.readUnsignedInt(), input.readUnsignedInt());
+        } else {
+            return new Fraction(input.readInt(), input.readInt()).unique();
+        }
+    }
+
+    /**
+     * Reads an array of rational numbers.
+     * This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
      */
-    static String toString(final long numerator, final long denominator) {
-        return new StringBuilder().append(numerator).append('/').append(denominator).toString();
+    final Number[] readFractions(final ChannelDataInput input, int count) throws IOException {
+        final Number[] values = new Number[count];
+        for (int i=0; i<count; i++) {
+            values[i] = nextFraction(input);
+        }
+        return values;
+    }
+
+    /**
+     * Reads a rational number, making sure that the value is unique if repeated.
+     * This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
+     */
+    final Number readFraction(final ChannelDataInput input, final long count) throws IOException {
+        final Number value = nextFraction(input);
+        for (long i=1; i<count; i++) {
+            ensureSingleton(value.doubleValue(), nextFraction(input).doubleValue(), count);
+        }
+        return value;
     }
 
     /**
@@ -470,7 +484,7 @@ enum Type {
      * @param  count     the number of values to read.
      * @throws IllegalArgumentException if {@code count} does not have the expected value.
      */
-    static void ensureSingleton(final long previous, final long actual, final long count) {
+    private static void ensureSingleton(final long previous, final long actual, final long count) {
         if (previous != actual) {
             // Even if the methods did not expected an array in argument, we are conceptually reading an array.
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
@@ -480,7 +494,7 @@ enum Type {
     /**
      * Same as {@link #ensureSingleton(long, long, long)} but with floating-point values.
      */
-    static void ensureSingleton(final double previous, final double actual, final long count) {
+    private static void ensureSingleton(final double previous, final double actual, final long count) {
         if (Double.doubleToLongBits(previous) != Double.doubleToLongBits(actual)) {
             throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
         }
@@ -638,6 +652,8 @@ enum Type {
 
     /**
      * Reads an arbitrary number of values as a Java array.
+     * This is usually (but not necessarily) an array of primitive type.
+     * It may be unsigned values packed in their signed counterpart.
      *
      * @param  input  the input from where to read the values.
      * @param  count  the amount of values.