/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed 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 it.geosolutions.jaiext.piecewise; import it.geosolutions.jaiext.range.Range; import it.geosolutions.jaiext.utilities.ImageUtilities; import java.util.Arrays; /** * Convenience class to group utilities methods for {@link DomainElement1D} and {@link Domain1D} implmentations. * * @author Simone Giannecchini, GeoSolutions. * */ public class PiecewiseUtilities { /** * Private Constructor, it cannot be instantiated */ private PiecewiseUtilities() { } /** * Checks whether or not two DomainElement1Ds input range overlaps * * @param domainElements to be checked * @param idx index to start with */ public static void domainElementsOverlap(DomainElement1D[] domainElements, int idx) { // Two domain elements have overlapping ranges; // Format an error message............... final Range range1 = domainElements[idx - 1].getRange(); final Range range2 = domainElements[idx].getRange(); final Number[] args = new Number[] { range1.getMin(), range1.getMax(), range2.getMin(), range2.getMax() }; String[] results = new String[4]; for (int j = 0; j < args.length; j++) { final double value = (args[j]).doubleValue(); if (Double.isNaN(value)) { String hex = Long.toHexString(Double.doubleToRawLongBits(value)); results[j] = "NaN(" + hex + ')'; } else { results[j] = value + ""; } } throw new IllegalArgumentException("Provided ranges are overlapping:" + results[0] + " : " + results[1] + " / " + results[2] + " : " + results[3]); } /** * Makes sure that an argument is non-null. * * @param name Argument name. * @param object User argument. * @throws IllegalArgumentException if {@code object} is null. */ public static void ensureNonNull(final String name, final Object object) throws IllegalArgumentException { if (object == null) { throw new IllegalArgumentException("Input object is null"); } } /** * Array binary search taking into account the fact that the input value to search can be NaN * * Note: This method is not private in order to allows testing by {@link }. */ public static int binarySearch(final double[] array, final double val) { int low = 0; int high = array.length - 1; final boolean keyIsNaN = Double.isNaN(val); while (low <= high) { final int mid = (low + high) >> 1; final double midVal = array[mid]; if (midVal < val) { // Neither val is NaN, midVal is smaller low = mid + 1; continue; } if (midVal > val) { // Neither val is NaN, midVal is larger high = mid - 1; continue; } /* * The following is an adaptation of evaluator's comments for bug #4471414 * (http://developer.java.sun.com/developer/bugParade/bugs/4471414. html). Extract from evaluator's comment: * * [This] code is not guaranteed to give the desired results because of laxity in IEEE 754 regarding NaN values. There are actually two * types of NaNs, signaling NaNs and quiet NaNs. Java doesn't support the features necessary to reliably distinguish the two. However, the * relevant point is that copying a signaling NaN may (or may not, at the implementors discretion) yield a quiet NaN -- a NaN with a * different bit pattern (IEEE 754 6.2). Therefore, on IEEE 754 compliant platforms it may be impossible to find a signaling NaN stored in * an array since a signaling NaN passed as an argument to binarySearch may get replaced by a quiet NaN. */ final long midRawBits = Double.doubleToRawLongBits(midVal); final long keyRawBits = Double.doubleToRawLongBits(val); if (midRawBits == keyRawBits) { return mid; // key found } final boolean midIsNaN = Double.isNaN(midVal); final boolean adjustLow; if (keyIsNaN) { // If (mid,key)==(!NaN, NaN): mid is lower. // If two NaN arguments, compare NaN bits. adjustLow = (!midIsNaN || midRawBits < keyRawBits); } else { // If (mid,key)==(NaN, !NaN): mid is greater. // Otherwise, case for (-0.0, 0.0) and (0.0, -0.0). adjustLow = (!midIsNaN && midRawBits < keyRawBits); } if (adjustLow) low = mid + 1; else high = mid - 1; } return -(low + 1); // key not found. } /** * Comparison between two double values */ public static int compare(final double v1, final double v2) { if (Double.isNaN(v1) && Double.isNaN(v2)) { final long bits1 = Double.doubleToRawLongBits(v1); final long bits2 = Double.doubleToRawLongBits(v2); if (bits1 < bits2) return -1; if (bits1 > bits2) return +1; } return Double.compare(v1, v2); } /** * Checks if the array is sorted */ public static boolean isSorted(final DefaultDomainElement1D[] domains) { if (domains == null) return true; for (int i = 1; i < domains.length; i++) { final DefaultDomainElement1D d1 = domains[i]; assert !(d1.getInputMinimum() > d1.getInputMaximum()) : d1; final DefaultDomainElement1D d0 = domains[i - 1]; assert !(d0.getInputMinimum() > d0.getInputMaximum()) : d0; if (compare(d0.getInputMaximum(), d1.getInputMinimum()) > 0) { return false; } } return true; } /** * Returns a {@code double} value for the specified number. If {@code direction} is non-zero, then this method will returns the closest * representable number of type {@code type} before or after the double value. * * @param type The range element class. {@code number} must be an instance of this class (this will not be checked). * @param number The number to transform to a {@code double} value. * @param direction -1 to return the previous representable number, +1 to return the next representable number, or 0 to return the number with no * change. */ public static double doubleValue(final Class<? extends Number> type, final Number number, final int direction) { assert (direction >= -1) && (direction <= +1) : direction; return ImageUtilities.rool(type, number.doubleValue(), direction); } /** * Returns a linear transform with the supplied scale and offset values. * * @param scale The scale factor. May be 0 for a constant transform. * @param offset The offset value. May be NaN. */ public static MathTransformation createLinearTransform1D(final double scale, final double offset) { return SingleDimensionTransformation.create(scale, offset); } /** * Create a linear transform mapping values from {@code sampleValueRange} to {@code geophysicsValueRange}. */ public static MathTransformation createLinearTransform1D(final Range sourceRange, final Range destinationRange) { final Class<? extends Number> sType = sourceRange.getDataType().getClassValue(); final Class<? extends Number> dType = destinationRange.getDataType().getClassValue(); /* * First, find the direction of the adjustment to apply to the ranges if we wanted all values to be inclusives. Then, check if the adjustment * is really needed: if the values of both ranges are inclusive or exclusive, then there is no need for an adjustment before computing the * coefficient of a linear relation. */ int sMinInc = sourceRange.isMinIncluded() ? 0 : +1; int sMaxInc = sourceRange.isMaxIncluded() ? 0 : -1; int dMinInc = destinationRange.isMinIncluded() ? 0 : +1; int dMaxInc = destinationRange.isMaxIncluded() ? 0 : -1; /* * Now, extracts the minimal and maximal values and computes the linear coefficients. */ final double minSource = doubleValue(sType, sourceRange.getMin(), sMinInc); final double maxSource = doubleValue(sType, sourceRange.getMax(), sMaxInc); final double minDestination = doubleValue(dType, destinationRange.getMin(), dMinInc); final double maxDestination = doubleValue(dType, destinationRange.getMax(), dMaxInc); // ///////////////////////////////////////////////////////////////// // // optimizations // // ///////////////////////////////////////////////////////////////// // // // // If the output range is a single value let's create a constant // transform // // // if (PiecewiseUtilities.compare(minDestination, maxDestination) == 0) return SingleDimensionTransformation.create(0, minDestination); // // // // If the input range is a single value this transform ca be created // only if we map to another single value // // // if (PiecewiseUtilities.compare(minSource, maxSource) == 0) throw new IllegalArgumentException("Impossible to map a single value to a range."); double scale = (maxDestination - minDestination) / (maxSource - minSource); // ///////////////////////////////////////////////////////////////// // // Take into account the fact that the maxSample and the minSample can // be // similar hence we have a constant transformation. // // ///////////////////////////////////////////////////////////////// if (Double.isNaN(scale)) scale = 0; final double offset = minDestination - scale * minSource; return createLinearTransform1D(scale, offset); } /** * Returns a hash code for the specified object, which may be an array. This method returns one of the following values: * <p> * <ul> * <li>If the supplied object is {@code null}, then this method returns 0.</li> * <li>Otherwise if the object is an array of objects, then {@link Arrays#deepHashCode(Object[])} is invoked.</li> * <li>Otherwise if the object is an array of primitive type, then the corresponding {@link Arrays#hashCode(double[]) Arrays.hashCode(...)} method * is invoked.</li> * <li>Otherwise {@link Object#hashCode()} is invoked. * <li> * </ul> * <p> * This method should be invoked <strong>only</strong> if the object type is declared exactly as {@code Object}, not as some subtype like * {@code Object[]}, {@code String} or {@code float[]}. In the later cases, use the appropriate {@link Arrays} method instead. * * @param object The object to compute hash code. May be {@code null}. * @return The hash code of the given object. */ public static int deepHashCode(final Object object) { if (object == null) { return 0; } if (object instanceof Object[]) { return Arrays.deepHashCode((Object[]) object); } if (object instanceof double[]) { return Arrays.hashCode((double[]) object); } if (object instanceof float[]) { return Arrays.hashCode((float[]) object); } if (object instanceof long[]) { return Arrays.hashCode((long[]) object); } if (object instanceof int[]) { return Arrays.hashCode((int[]) object); } if (object instanceof short[]) { return Arrays.hashCode((short[]) object); } if (object instanceof byte[]) { return Arrays.hashCode((byte[]) object); } if (object instanceof char[]) { return Arrays.hashCode((char[]) object); } if (object instanceof boolean[]) { return Arrays.hashCode((boolean[]) object); } return object.hashCode(); } /** * A prime number used for hash code computation. */ private static final int PRIME_NUMBER = 37; /** * Alters the given seed with the hash code value computed from the given value. The givan object may be null. This method do <strong>not</strong> * iterates recursively in array elements. If array needs to be hashed, use one of {@link Arrays} method or {@link #deepHashCode deepHashCode} * instead. * <p> * <b>Note on assertions:</b> There is no way to ensure at compile time that this method is not invoked with an array argument, while doing so * would usually be a program error. Performing a systematic argument check would impose a useless overhead for correctly implemented * {@link Object#hashCode} methods. As a compromise we perform this check at runtime only if assertions are enabled. Using assertions for argument * check in a public API is usually a deprecated practice, but we make an exception for this particular method. * * @param value The value whose hash code to compute, or {@code null}. * @param seed The hash code value computed so far. If this method is invoked for the first field, then any arbitrary value (preferably different * for each class) is okay. * @return An updated hash code value. * @throws AssertionError If assertions are enabled and the given value is an array. */ public static int hash(Object value, int seed) throws AssertionError { seed *= PRIME_NUMBER; if (value != null) { assert !value.getClass().isArray() : value; seed += value.hashCode(); } return seed; } /** * Alters the given seed with the hash code value computed from the given value. * * @param value The value whose hash code to compute. * @param seed The hash code value computed so far. If this method is invoked for the first field, then any arbitrary value (preferably different * for each class) is okay. * @return An updated hash code value. */ public static int hash(double value, int seed) { return hash(Double.doubleToLongBits(value), seed); } /** * Alters the given seed with the hash code value computed from the given value. {@code byte} and {@code short} primitive types are handled by * this method as well through implicit widening conversion. * * @param value The value whose hash code to compute. * @param seed The hash code value computed so far. If this method is invoked for the first field, then any arbitrary value (preferably different * for each class) is okay. * @return An updated hash code value. */ public static int hash(long value, int seed) { return seed * PRIME_NUMBER + (((int) value) ^ ((int) (value >>> 32))); } /** * Returns {@code true} if the given doubles are equals. Positive and negative zero are considered different, while a NaN value is considered * equal to other NaN values. * * @param o1 The first value to compare. * @param o2 The second value to compare. * @return {@code true} if both values are equal. * * @see Double#equals */ public static boolean equals(double o1, double o2) { if (Double.doubleToLongBits(o1) == Double.doubleToLongBits(o2)) return true; double tol = getTolerance(); final double min = o1 - Math.signum(o1) * o1 * tol; final double max = o1 + Math.signum(o1) * o1 * tol; return min <= o2 && o2 <= max; } public static boolean equals(Object object1, Object object2) { assert object1 == null || !object1.getClass().isArray() : object1; assert object2 == null || !object2.getClass().isArray() : object2; return (object1 == object2) || (object1 != null && object1.equals(object2)); } /** * Gathers the tolerance for floating point comparisons * * @return The tolerance set in the JVM properties, or its default value if not set */ private static double getTolerance() { Double tol = Double.parseDouble(System.getProperty("jaiext.piecewise.tolerance")); if (tol == null) { return 0.0d; } return tol; } public static boolean equals(Range outputRange, Range outputRange2) { return outputRange.equals(outputRange2); } }