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.