/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.referencing.piecewise; import java.util.Arrays; import org.geotools.referencing.operation.transform.LinearTransform1D; import org.geotools.renderer.i18n.ErrorKeys; import org.geotools.renderer.i18n.Errors; import org.geotools.resources.XMath; import org.geotools.util.NumberRange; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.operation.MathTransform1D; /** * Convenience class to group utilities methods for {@link DomainElement1D} and * {@link Domain1D} implmentations. * * @author Simone Giannecchini, GeoSolutions. * */ class PiecewiseUtilities { /** * */ private PiecewiseUtilities() { } /** * Checks whether or not two DomainElement1Ds input range overlaps * * @param domainElements * to be checked * @param idx * index to start with */ static void domainElementsOverlap(DomainElement1D[] domainElements, int idx) { // Two domain elements have overlapping ranges; // Format an error message............... final NumberRange<? extends Number> range1 = domainElements[idx - 1] .getRange(); final NumberRange<? extends Number> range2 = domainElements[idx].getRange(); final Comparable[] args = new Comparable[] { range1.getMinValue(), range1.getMaxValue(), range2.getMinValue(), range2.getMaxValue() }; for (int j = 0; j < args.length; j++) { if (args[j] instanceof Number) { final double value = ((Number) args[j]).doubleValue(); if (Double.isNaN(value)) { String hex = Long.toHexString(Double .doubleToRawLongBits(value)); args[j] = "NaN(" + hex + ')'; } } } throw new IllegalArgumentException(Errors.format( ErrorKeys.RANGE_OVERLAP_$4, args)); } /** * Makes sure that an argument is non-null. * * @param name * Argument name. * @param object * User argument. * @throws IllegalArgumentException * if {@code object} is null. */ static void ensureNonNull(final String name, final Object object) throws IllegalArgumentException { if (object == null) { throw new IllegalArgumentException(Errors.format( ErrorKeys.NULL_ARGUMENT_$1, name)); } } /** * Effectue une recherche bi-lin�aire de la valeur sp�cifi�e. Cette m�thode * est semblable � {@link Arrays#binarySearch(double[],double)}, except� * qu'elle peut distinguer diff�rentes valeurs de NaN. * * Note: This method is not private in order to allows testing by {@link }. */ 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. } /** * Compare deux valeurs de type {@code double}. Cette m�thode est similaire * � {@link Double#compare(double,double)}, except� qu'elle ordonne aussi * les diff�rentes valeurs NaN. */ 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); } /** * V�rifie si le tableau de cat�gories sp�cifi� est bien en ordre croissant. * La comparaison ne tient pas compte des valeurs {@code NaN}. Cette * m�thode n'est utilis�e que pour les {@code assert}. */ 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; } /** * Ensure the specified point is one-dimensional. */ static void checkDimension(final DirectPosition point) { final int dim = point.getDimension(); if (dim != 1) { throw new MismatchedDimensionException(Errors.format( ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(1), new Integer(dim))); } } /** * 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. */ static double doubleValue(final Class<? extends Number> type, final Number number, final int direction) { assert (direction >= -1) && (direction <= +1) : direction; return XMath.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. */ static MathTransform1D createLinearTransform1D( final double scale, final double offset) { return LinearTransform1D.create(scale, offset); } /** * Create a linear transform mapping values from {@code sampleValueRange} to * {@code geophysicsValueRange}. */ static MathTransform1D createLinearTransform1D( final NumberRange<? extends Number> sourceRange, final NumberRange<? extends Number> destinationRange) { final Class<? extends Number> sType = sourceRange.getElementClass(); final Class<? extends Number> dType = destinationRange.getElementClass(); /* * 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.getMinValue(), sMinInc); final double maxSource = doubleValue(sType, sourceRange.getMaxValue(), sMaxInc); final double minDestination = doubleValue(dType, destinationRange .getMinValue(), dMinInc); final double maxDestination = doubleValue(dType, destinationRange .getMaxValue(), dMaxInc); // ///////////////////////////////////////////////////////////////// // // optimizations // // ///////////////////////////////////////////////////////////////// // // // // If the output range is a single value let's create a constant // transform // // // if (PiecewiseUtilities.compare(minDestination, maxDestination) == 0) return LinearTransform1D.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); } }