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 <martin.desruisse...@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.