0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2003-2006, Geotools Project Management Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * This package contains documentation from OpenGIS specifications.
0018: * OpenGIS consortium's work is fully acknowledged here.
0019: */
0020: package org.geotools.coverage;
0021:
0022: // J2SE dependencies and extensions
0023: import java.awt.image.ColorModel;
0024: import java.awt.image.DataBuffer;
0025: import java.awt.image.RasterFormatException;
0026: import java.awt.image.RenderedImage;
0027: import java.io.IOException;
0028: import java.io.ObjectInputStream;
0029: import java.io.Serializable;
0030: import java.util.AbstractList;
0031: import java.util.Arrays;
0032: import java.util.Comparator;
0033: import java.util.Locale;
0034: import javax.units.Unit;
0035:
0036: // JAI dependencies
0037: import javax.media.jai.iterator.WritableRectIter;
0038:
0039: // OpenGIS dependencies
0040: import org.opengis.referencing.operation.MathTransform;
0041: import org.opengis.referencing.operation.MathTransform1D;
0042: import org.opengis.referencing.operation.Matrix;
0043: import org.opengis.referencing.operation.TransformException;
0044: import org.opengis.geometry.DirectPosition;
0045: import org.opengis.geometry.MismatchedDimensionException;
0046: import org.opengis.util.InternationalString;
0047:
0048: // Geotools dependencies
0049: import org.geotools.geometry.GeneralDirectPosition;
0050: import org.geotools.referencing.operation.matrix.Matrix1;
0051: import org.geotools.referencing.wkt.UnformattableObjectException;
0052: import org.geotools.resources.Utilities;
0053: import org.geotools.resources.i18n.Errors;
0054: import org.geotools.resources.i18n.ErrorKeys;
0055: import org.geotools.resources.i18n.Vocabulary;
0056: import org.geotools.resources.i18n.VocabularyKeys;
0057: import org.geotools.util.AbstractInternationalString;
0058: import org.geotools.util.NumberRange;
0059:
0060: /**
0061: * An immutable list of categories. Categories are sorted by their sample values.
0062: * Overlapping ranges of sample values are not allowed. A {@code CategoryList} can
0063: * contains a mix of qualitative and quantitative categories. The {@link #getCategory}
0064: * method is responsible for finding the right category for an arbitrary sample value.
0065: *
0066: * Instances of {@link CategoryList} are immutable and thread-safe.
0067: *
0068: * @since 2.1
0069: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/CategoryList.java $
0070: * @version $Id: CategoryList.java 25449 2007-05-07 12:21:16Z desruisseaux $
0071: * @author Martin Desruisseaux
0072: */
0073: class CategoryList extends AbstractList implements MathTransform1D,
0074: Comparator, Serializable {
0075: /**
0076: * Serial number for interoperability with different versions.
0077: */
0078: private static final long serialVersionUID = 2647846361059903365L;
0079:
0080: /**
0081: * The inverse transform, never {@code null}.
0082: * The following rule must hold:
0083: *
0084: * <ul>
0085: * <li>If {@code this} is an instance of {@link CategoryList}, then
0086: * {@code inverse} must be an instance of {@link GeophysicsCategoryList}.</li>
0087: * <li>If {@code this} is an instance of {@link GeophysicsCategoryList}, then
0088: * {@code inverse} must be an instance of {@link CategoryList}.</li>
0089: * </ul>
0090: */
0091: final CategoryList inverse;
0092:
0093: /**
0094: * The range of values in this category list. This is the union of the range of values
0095: * of every categories, excluding {@code NaN} values. This field will be computed
0096: * only when first requested.
0097: */
0098: private transient NumberRange range;
0099:
0100: /**
0101: * List of {@link Category#minimum} values for each category in {@link #categories}.
0102: * This array <strong>must</strong> be in increasing order. Actually, this is the
0103: * need to sort this array that determines the element order in {@link #categories}.
0104: */
0105: private final double[] minimums;
0106:
0107: /**
0108: * The list of categories to use for decoding samples. This list most be sorted
0109: * in increasing order of {@link Category#minimum}. This {@link CategoryList}
0110: * object may be used as a {@link Comparator} for that. Qualitative categories
0111: * (with NaN values) are last.
0112: */
0113: private final Category[] categories;
0114:
0115: /**
0116: * The "main" category, or {@code null} if there is none. The main category
0117: * is the quantitative category with the widest range of sample values.
0118: */
0119: private final Category main;
0120:
0121: /**
0122: * The "nodata" category (never {@code null}). The "nodata" category is a
0123: * category mapping the geophysics {@link Double#NaN} value. If none has been
0124: * found, a default "nodata" category is used. This category is used to transform
0125: * geophysics values to sample values into rasters when no suitable category has
0126: * been found for a given geophysics value.
0127: */
0128: final Category nodata;
0129:
0130: /**
0131: * The category to use if {@link #getCategory(double)} is invoked with a sample value
0132: * greater than all sample ranges in this category list. This is usually a reference to
0133: * the last category to have a range of real values. A {@code null} value means that no
0134: * fallback should be used. By extension, a {@code null} value also means that
0135: * {@link #getCategory} should not try to find any fallback at all if the requested
0136: * sample value do not falls in a category range.
0137: */
0138: private final Category overflowFallback;
0139:
0140: /**
0141: * The last used category. We assume that this category is the most likely
0142: * to be requested in the next {@code transform(...)} invocation.
0143: */
0144: private transient Category last;
0145:
0146: /**
0147: * {@code true} if there is gaps between categories, or {@code false} otherwise.
0148: * A gap is found if for example the range of value is [-9999 .. -9999] for the first
0149: * category and [0 .. 1000] for the second one.
0150: */
0151: private final boolean hasGaps;
0152:
0153: /**
0154: * The name for this category list. Will be constructed only when first needed.
0155: *
0156: * @see #getName
0157: */
0158: private transient InternationalString name;
0159:
0160: /**
0161: * Construct a category list using the specified array of categories.
0162: *
0163: * @param categories The list of categories.
0164: * @param units The geophysics unit, or {@code null} if none.
0165: * @throws IllegalArgumentException if two or more categories
0166: * have overlapping sample value range.
0167: */
0168: public CategoryList(final Category[] categories, final Unit units)
0169: throws IllegalArgumentException {
0170: this (categories, units, false, null);
0171: assert isScaled(false);
0172: }
0173:
0174: /**
0175: * Construct a category list using the specified array of categories.
0176: *
0177: * <STRONG>This constructor is for internal use only</STRONG>
0178: *
0179: * It is not private only because {@link GeophysicsCategoryList} need this constructor.
0180: *
0181: * @param categories The list of categories.
0182: * @param units The geophysics unit, or {@code null} if none.
0183: * @param searchNearest The policy when {@link #getCategory} doesn't find an exact match
0184: * for a sample value. {@code true} means that it should search for the nearest
0185: * category, while {@code false} means that it should returns {@code null}.
0186: * @param inverse The inverse transform, or {@code null} to build it automatically.
0187: * <STRONG>This argument can be non-null only if invoked from
0188: * {@link GeophysicsCategoryList} constructor</STRONG>.
0189: * @throws IllegalArgumentException if two or more categories have overlapping sample value
0190: * range.
0191: */
0192: CategoryList(Category[] categories, Unit units,
0193: boolean searchNearest, CategoryList inverse)
0194: throws IllegalArgumentException {
0195: /*
0196: * Check if we are constructing a geophysics category list, then rescale all cagegories
0197: * according. We may loose the user intend by doing so (he may have specified explicitly
0198: * a list of GeophysicsCategory), but this is the SampleDimension's job to keep trace of
0199: * it.
0200: */
0201: final boolean isGeophysics = (this instanceof GeophysicsCategoryList);
0202: assert (inverse != null) == isGeophysics;
0203: this .categories = categories = (Category[]) categories.clone();
0204: for (int i = 0; i < categories.length; i++) {
0205: categories[i] = categories[i].geophysics(isGeophysics);
0206: }
0207: Arrays.sort(categories, this );
0208: assert isSorted(categories);
0209: assert isScaled(isGeophysics);
0210: /*
0211: * Construct the array of Category.minimum values. During
0212: * the loop, we make sure there is no overlapping ranges.
0213: */
0214: boolean hasGaps = false;
0215: minimums = new double[categories.length];
0216: for (int i = 0; i < categories.length; i++) {
0217: final double minimum = minimums[i] = categories[i].minimum;
0218: if (i != 0) {
0219: assert !(minimum < minimums[i - 1]) : minimum; // Use '!' to accept NaN.
0220: final Category previous = categories[i - 1];
0221: if (compare(minimum, previous.maximum) <= 0) {
0222: // Two categories have overlapping range;
0223: // Format an error message...............
0224: final NumberRange range1 = categories[i - 1]
0225: .getRange();
0226: final NumberRange range2 = categories[i - 0]
0227: .getRange();
0228: final Comparable[] args = new Comparable[] {
0229: range1.getMinValue(), range1.getMaxValue(),
0230: range2.getMinValue(), range2.getMaxValue() };
0231: for (int j = 0; j < args.length; j++) {
0232: if (args[j] instanceof Number) {
0233: final float value = ((Number) args[j])
0234: .floatValue();
0235: if (Float.isNaN(value)) {
0236: String hex = Integer.toHexString(Float
0237: .floatToRawIntBits(value));
0238: args[j] = "NaN(" + hex + ')';
0239: }
0240: }
0241: }
0242: throw new IllegalArgumentException(Errors.format(
0243: ErrorKeys.RANGE_OVERLAP_$4, args));
0244: }
0245: // Check if there is a gap between this category and the previous one.
0246: if (!Double.isNaN(minimum)
0247: && minimum != previous.getRange().getMaximum(
0248: false)) {
0249: hasGaps = true;
0250: }
0251: }
0252: }
0253: this .hasGaps = hasGaps;
0254: /*
0255: * Search for the "nodata" category. This loop looks
0256: * for a qualitative category with the NaN value.
0257: */
0258: Category nodata = Category.NODATA;
0259: final long nodataBits = Double.doubleToRawLongBits(Double.NaN);
0260: for (int i = categories.length; --i >= 0;) {
0261: final Category candidate = categories[i];
0262: final double value = candidate.geophysics(true).minimum;
0263: if (Double.isNaN(value)) {
0264: nodata = candidate;
0265: if (Double.doubleToRawLongBits(value) == nodataBits) {
0266: // Give a preference for the standard Double.NaN.
0267: // We should have only one such value, since the
0268: // range check above prevents range overlapping.
0269: break;
0270: }
0271: }
0272: }
0273: this .nodata = nodata;
0274: /*
0275: * Search for what seems to be the "main" category. This loop looks for the
0276: * quantitative category (if there is one) with the widest range of sample values.
0277: */
0278: double range = 0;
0279: Category main = null;
0280: for (int i = categories.length; --i >= 0;) {
0281: final Category candidate = categories[i];
0282: if (candidate.isQuantitative()) {
0283: final Category candidatePeer = candidate
0284: .geophysics(false);
0285: final double candidateRange = candidatePeer.maximum
0286: - candidatePeer.minimum;
0287: if (candidateRange >= range) {
0288: range = candidateRange;
0289: main = candidate;
0290: }
0291: }
0292: }
0293: this .main = main;
0294: this .last = main;
0295: /*
0296: * Search for the fallback if {@link #getCategory(double)} is invoked with a sample
0297: * value greater than all ranges of sample values. This is the last category to have
0298: * a range of real numbers.
0299: */
0300: Category overflowFallback = null;
0301: if (searchNearest) {
0302: for (int i = categories.length; --i >= 0;) {
0303: final Category category = categories[i];
0304: if (!Double.isNaN(category.maximum)) {
0305: overflowFallback = category;
0306: break;
0307: }
0308: }
0309: }
0310: this .overflowFallback = overflowFallback;
0311: /*
0312: * Set the inverse transform. If no inverse transform has been explicitly specified, then
0313: * this is the "normal" construction call (i.e. not the special construction performed by
0314: * GeophysicsCategoryList) and we create our internal inverse object.
0315: */
0316: if (inverse == null) {
0317: inverse = new GeophysicsCategoryList(categories, units,
0318: this );
0319: }
0320: this .inverse = inverse;
0321: assert (this instanceof GeophysicsCategoryList) != (inverse instanceof GeophysicsCategoryList);
0322: }
0323:
0324: /**
0325: * Compare {@link Category} objects according their {@link Category#minimum} value.
0326: * This is used for sorting the {@link #categories} array at construction time.
0327: */
0328: public final int compare(final Object o1, final Object o2) {
0329: return compare(((Category) o1).minimum, ((Category) o2).minimum);
0330: }
0331:
0332: /**
0333: * Compare deux valeurs de type {@code double}. Cette méthode
0334: * est similaire à {@link Double#compare(double,double)}, excepté
0335: * qu'elle ordonne aussi les différentes valeurs NaN.
0336: */
0337: private static int compare(final double v1, final double v2) {
0338: if (Double.isNaN(v1) && Double.isNaN(v2)) {
0339: final long bits1 = Double.doubleToRawLongBits(v1);
0340: final long bits2 = Double.doubleToRawLongBits(v2);
0341: if (bits1 < bits2)
0342: return -1;
0343: if (bits1 > bits2)
0344: return +1;
0345: }
0346: return Double.compare(v1, v2);
0347: }
0348:
0349: /**
0350: * Vérifie si le tableau de catégories spécifié est bien en ordre croissant.
0351: * La comparaison ne tient pas compte des valeurs {@code NaN}. Cette
0352: * méthode n'est utilisée que pour les {@code assert}.
0353: */
0354: static boolean isSorted(final Category[] categories) {
0355: for (int i = 1; i < categories.length; i++) {
0356: Category c;
0357: assert !((c = categories[i - 0]).minimum > c.maximum) : c;
0358: assert !((c = categories[i - 1]).minimum > c.maximum) : c;
0359: if (compare(categories[i - 1].maximum,
0360: categories[i].minimum) > 0) {
0361: return false;
0362: }
0363: }
0364: return true;
0365: }
0366:
0367: /**
0368: * Effectue une recherche bi-linéaire de la valeur spécifiée. Cette
0369: * méthode est semblable à {@link Arrays#binarySearch(double[],double)},
0370: * excepté qu'elle peut distinguer différentes valeurs de NaN.
0371: *
0372: * Note: This method is not private in order to allows testing by {@link CategoryTest}.
0373: */
0374: static int binarySearch(final double[] array, final double key) {
0375: int low = 0;
0376: int high = array.length - 1;
0377: final boolean keyIsNaN = Double.isNaN(key);
0378: while (low <= high) {
0379: final int mid = (low + high) >> 1;
0380: final double midVal = array[mid];
0381: if (midVal < key) { // Neither val is NaN, midVal is smaller
0382: low = mid + 1;
0383: continue;
0384: }
0385: if (midVal > key) { // Neither val is NaN, midVal is larger
0386: high = mid - 1;
0387: continue;
0388: }
0389: /*
0390: * The following is an adaptation of evaluator's comments for bug #4471414
0391: * (http://developer.java.sun.com/developer/bugParade/bugs/4471414.html).
0392: * Extract from evaluator's comment:
0393: *
0394: * [This] code is not guaranteed to give the desired results because
0395: * of laxity in IEEE 754 regarding NaN values. There are actually two
0396: * types of NaNs, signaling NaNs and quiet NaNs. Java doesn't support
0397: * the features necessary to reliably distinguish the two. However,
0398: * the relevant point is that copying a signaling NaN may (or may not,
0399: * at the implementors discretion) yield a quiet NaN -- a NaN with a
0400: * different bit pattern (IEEE 754 6.2). Therefore, on IEEE 754 compliant
0401: * platforms it may be impossible to find a signaling NaN stored in an
0402: * array since a signaling NaN passed as an argument to binarySearch may
0403: * get replaced by a quiet NaN.
0404: */
0405: final long midRawBits = Double.doubleToRawLongBits(midVal);
0406: final long keyRawBits = Double.doubleToRawLongBits(key);
0407: if (midRawBits == keyRawBits) {
0408: return mid; // key found
0409: }
0410: final boolean midIsNaN = Double.isNaN(midVal);
0411: final boolean adjustLow;
0412: if (keyIsNaN) {
0413: // If (mid,key)==(!NaN, NaN): mid is lower.
0414: // If two NaN arguments, compare NaN bits.
0415: adjustLow = (!midIsNaN || midRawBits < keyRawBits);
0416: } else {
0417: // If (mid,key)==(NaN, !NaN): mid is greater.
0418: // Otherwise, case for (-0.0, 0.0) and (0.0, -0.0).
0419: adjustLow = (!midIsNaN && midRawBits < keyRawBits);
0420: }
0421: if (adjustLow)
0422: low = mid + 1;
0423: else
0424: high = mid - 1;
0425: }
0426: return -(low + 1); // key not found.
0427: }
0428:
0429: /**
0430: * If {@code toGeophysics} is {@code true}, returns a list of categories scaled
0431: * to geophysics values. This method always returns a list of categories in which
0432: * <code>{@link Category#geophysics(boolean) Category.geophysics}(toGeophysics)</code>
0433: * has been invoked for each category.
0434: */
0435: public CategoryList geophysics(final boolean toGeophysics) {
0436: final CategoryList scaled = toGeophysics ? inverse : this ;
0437: assert scaled.isScaled(toGeophysics);
0438: return scaled;
0439: }
0440:
0441: /**
0442: * Verify if all categories are scaled to the specified state.
0443: * This is used mostly in assertion statements.
0444: *
0445: * @param toGeophysics The state to test.
0446: * @return {@code true} if all categories are in the specified state.
0447: */
0448: final boolean isScaled(final boolean toGeophysics) {
0449: return isScaled(categories, toGeophysics);
0450: }
0451:
0452: /**
0453: * Verify if all categories are scaled to the specified state.
0454: *
0455: * @param categories The categories to test.
0456: * @param toGeophysics The state to test.
0457: * @return {@code true} if all categories are in the specified state.
0458: */
0459: static boolean isScaled(final Category[] categories,
0460: final boolean toGeophysics) {
0461: for (int i = 0; i < categories.length; i++) {
0462: final Category c = categories[i];
0463: if (c.geophysics(toGeophysics) != c) {
0464: return false;
0465: }
0466: }
0467: return true;
0468: }
0469:
0470: /**
0471: * Returns the name of this object. The default implementation returns the name
0472: * of what seems to be the "main" category (i.e. the quantitative category with
0473: * the widest range of sample values) followed by the geophysics value range.
0474: */
0475: public final InternationalString getName() {
0476: if (name == null) {
0477: name = new Name();
0478: }
0479: return name;
0480: }
0481:
0482: /**
0483: * The name for this category list. Will be created only when first needed.
0484: */
0485: private final class Name extends AbstractInternationalString {
0486: /** Returns the name in the specified locale. */
0487: public String toString(final Locale locale) {
0488: final StringBuffer buffer = new StringBuffer(30);
0489: if (main != null) {
0490: buffer.append(main.getName().toString(locale));
0491: } else {
0492: buffer.append('(');
0493: buffer.append(Vocabulary.getResources(locale)
0494: .getString(VocabularyKeys.UNTITLED));
0495: buffer.append(')');
0496: }
0497: buffer.append(' ');
0498: return String.valueOf(geophysics(true).formatRange(buffer,
0499: locale));
0500: }
0501:
0502: /** Returns the name in the default locale. */
0503: public String toString() {
0504: return toString(Locale.getDefault());
0505: }
0506: }
0507:
0508: /**
0509: * Returns the unit information for quantitative categories in this list. May returns
0510: * {@code null} if there is no quantitative categories in this list, or if there is no
0511: * unit information.
0512: * <p>
0513: * This method is to be overridden by {@link GeophysicsCategoryList}. The default implementation
0514: * returns {@code null} since sample values are not geophysics values as long as they have not
0515: * been transformed. The {@link GridSampleDimension} class will invoke
0516: * {@code geophysics(true).getUnits()} in order to get a non-null unit.
0517: */
0518: public Unit getUnits() {
0519: return null;
0520: }
0521:
0522: /**
0523: * Returns the range of values in this category list. This is the union of the range
0524: * of values of every categories, excluding {@code NaN} values. A {@link NumberRange}
0525: * object give more informations than {@link org.opengis.CV_SampleDimension#getMinimum}
0526: * and {@link org.opengis.CV_SampleDimension#getMaximum} since it contains also the
0527: * type (integer, float, etc.) and inclusion/exclusion informations.
0528: *
0529: * @return The range of values. May be {@code null} if this category list has no
0530: * quantitative category.
0531: *
0532: * @see Category#getRange
0533: */
0534: public final NumberRange getRange() {
0535: if (range == null) {
0536: NumberRange range = null;
0537: for (int i = 0; i < categories.length; i++) {
0538: final NumberRange extent = categories[i].getRange();
0539: if (!Double.isNaN(extent.getMinimum())
0540: && !Double.isNaN(extent.getMaximum())) {
0541: if (range != null) {
0542: range = NumberRange.wrap(range.union(extent));
0543: } else {
0544: range = extent;
0545: }
0546: }
0547: }
0548: this .range = range;
0549: }
0550: return range;
0551: }
0552:
0553: /**
0554: * Format the range of geophysics values.
0555: *
0556: * @param buffer The buffer where to write the range of geophysics values.
0557: * @param locale The locale to use for formatting numbers.
0558: * @return The {@code buffer} for convenience.
0559: */
0560: private StringBuffer formatRange(StringBuffer buffer,
0561: final Locale locale) {
0562: final NumberRange range = getRange();
0563: buffer.append('[');
0564: if (range != null) {
0565: buffer = format(range.getMinimum(), false, locale, buffer);
0566: buffer.append(" ... ");
0567: buffer = format(range.getMaximum(), true, locale, buffer);
0568: } else {
0569: final Unit unit = getUnits();
0570: if (unit != null) {
0571: buffer.append(unit);
0572: }
0573: }
0574: buffer.append(']');
0575: return buffer;
0576: }
0577:
0578: /**
0579: * Format the specified value using the specified locale convention. This method is to be
0580: * overridden by {@link GeophysicsCategoryList}. The default implementation do not format
0581: * the value very properly, since most invocation will be done on
0582: * {@code geophysics(true).format(...)} anyway.
0583: *
0584: * @param value The value to format.
0585: * @param writeUnit {@code true} if unit symbol should be formatted after the number.
0586: * Ignored if this category list has no unit.
0587: * @param locale The locale, or {@code null} for a default one.
0588: * @param buffer The buffer where to format.
0589: * @return The buffer {@code buffer} for convenience.
0590: */
0591: StringBuffer format(final double value, final boolean writeUnits,
0592: final Locale locale, StringBuffer buffer) {
0593: return buffer.append(value);
0594: }
0595:
0596: /**
0597: * Returns a color model for this category list. This method builds up the color model
0598: * from each category's colors (as returned by {@link Category#getColors}).
0599: *
0600: * @param visibleBand The band to be made visible (usually 0). All other bands, if any
0601: * will be ignored.
0602: * @param numBands The number of bands for the color model (usually 1). The returned color
0603: * model will renderer only the {@code visibleBand} and ignore the others, but
0604: * the existence of all {@code numBands} will be at least tolerated. Supplemental
0605: * bands, even invisible, are useful for processing with Java Advanced Imaging.
0606: * @return The requested color model, suitable for {@link RenderedImage} objects with values
0607: * in the <code>{@link #getRange}</code> range.
0608: */
0609: public final ColorModel getColorModel(final int visibleBand,
0610: final int numBands) {
0611: int type = DataBuffer.TYPE_FLOAT;
0612: final NumberRange range = getRange();
0613: final Class rt = range.getElementClass();
0614: if (Byte.class.equals(rt) || Short.class.equals(rt)
0615: || Integer.class.equals(rt)) {
0616: final int min = ((Number) range.getMinValue()).intValue();
0617: final int max = ((Number) range.getMaxValue()).intValue();
0618: if (min >= 0) {
0619: if (max < 0x100) {
0620: type = DataBuffer.TYPE_BYTE;
0621: } else if (max < 0x10000) {
0622: type = DataBuffer.TYPE_USHORT;
0623: } else {
0624: type = DataBuffer.TYPE_INT;
0625: }
0626: } else if (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE) {
0627: type = DataBuffer.TYPE_SHORT;
0628: } else {
0629: type = DataBuffer.TYPE_INT;
0630: }
0631: }
0632: return ColorModelFactory.getColorModel(categories, type,
0633: visibleBand, numBands);
0634: }
0635:
0636: /**
0637: * Returns a color model for this category list. This method builds up the color model
0638: * from each category's colors (as returned by {@link Category#getColors}).
0639: *
0640: * @param visibleBand The band to be made visible (usually 0). All other bands, if any
0641: * will be ignored.
0642: * @param numBands The number of bands for the color model (usually 1). The returned color
0643: * model will renderer only the {@code visibleBand} and ignore the others, but
0644: * the existence of all {@code numBands} will be at least tolerated. Supplemental
0645: * bands, even invisible, are useful for processing with Java Advanced Imaging.
0646: * @param type The transfer type used in the sample model
0647: * @return The requested color model, suitable for {@link RenderedImage} objects with values
0648: * in the <code>{@link #getRange}</code> range.
0649: */
0650: public final ColorModel getColorModel(final int visibleBand,
0651: final int numBands, final int type) {
0652: return ColorModelFactory.getColorModel(categories, type,
0653: visibleBand, numBands);
0654: }
0655:
0656: /**
0657: * Returns the category of the specified sample value.
0658: * If no category fits, then this method returns {@code null}.
0659: *
0660: * @param sample The value.
0661: * @return The category of the supplied value, or {@code null}.
0662: */
0663: public final Category getCategory(final double sample) {
0664: /*
0665: * Recherche à quelle catégorie pourrait appartenir la valeur.
0666: * Note: Les valeurs 'NaN' sont à la fin du tableau 'values'. Donc:
0667: *
0668: * 1) Si 'value' est NaN, alors 'i' pointera forcément sur une catégorie NaN.
0669: * 2) Si 'value' est réel, alors 'i' peut pointer sur une des catégories de
0670: * valeurs réels ou sur la première catégorie de NaN.
0671: */
0672: int i = binarySearch(minimums, sample); // Special 'binarySearch' for NaN
0673: if (i >= 0) {
0674: // The value is exactly equals to one of Category.minimum,
0675: // or is one of NaN values. There is nothing else to do.
0676: assert Double.doubleToRawLongBits(sample) == Double
0677: .doubleToRawLongBits(minimums[i]);
0678: return categories[i];
0679: }
0680: if (Double.isNaN(sample)) {
0681: // The value is NaN, but not one of the registered ones.
0682: // Consequently, we can't map a category to this value.
0683: return null;
0684: }
0685: assert i == Arrays.binarySearch(minimums, sample) : i;
0686: // 'binarySearch' found the index of "insertion point" (~i). This means that
0687: // 'sample' is lower than 'Category.minimum' at this index. Consequently, if
0688: // this value fits in a category's range, it fits in the previous category (~i-1).
0689: i = ~i - 1;
0690: if (i >= 0) {
0691: final Category category = categories[i];
0692: assert sample > category.minimum : sample;
0693: if (sample <= category.maximum) {
0694: return category;
0695: }
0696: if (overflowFallback != null) {
0697: if (++i < categories.length) {
0698: final Category upper = categories[i];
0699: // ASSERT: if 'upper.minimum' was smaller than 'value', it should has been
0700: // found by 'binarySearch'. We use '!' in order to accept NaN values.
0701: assert !(upper.minimum <= sample) : sample;
0702: return (upper.minimum - sample < sample
0703: - category.maximum) ? upper : category;
0704: }
0705: return overflowFallback;
0706: }
0707: } else if (overflowFallback != null) {
0708: // If the value is smaller than the smallest Category.minimum, returns
0709: // the first category (except if there is only NaN categories).
0710: if (categories.length != 0) {
0711: final Category category = categories[0];
0712: if (!Double.isNaN(category.minimum)) {
0713: return category;
0714: }
0715: }
0716: }
0717: return null;
0718: }
0719:
0720: /**
0721: * Format a sample value. If {@code value} is a real number, then the value may
0722: * be formatted with the appropriate number of digits and the units symbol. Otherwise,
0723: * if {@code value} is {@code NaN}, then the category name is returned.
0724: *
0725: * @param value The sample value (may be {@code NaN}).
0726: * @param locale Locale to use for formatting, or {@code null} for the default locale.
0727: * @return A string representation of the sample value.
0728: */
0729: public final String format(final double value, final Locale locale) {
0730: if (Double.isNaN(value)) {
0731: Category category = last;
0732: if (!(value >= category.minimum && value <= category.maximum)
0733: && Double.doubleToRawLongBits(value) != Double
0734: .doubleToRawLongBits(category.minimum)) {
0735: category = getCategory(value);
0736: if (category == null) {
0737: return Vocabulary.getResources(locale).getString(
0738: VocabularyKeys.UNTITLED);
0739: }
0740: last = category;
0741: }
0742: return category.getName().toString(null);
0743: }
0744: return format(value, true, locale, new StringBuffer())
0745: .toString();
0746: }
0747:
0748: //////////////////////////////////////////////////////////////////////////////////////////
0749: //////// ////////
0750: //////// I M P L E M E N T A T I O N O F List I N T E R F A C E ////////
0751: //////// ////////
0752: //////////////////////////////////////////////////////////////////////////////////////////
0753: /**
0754: * Returns the number of categories in this list.
0755: */
0756: public final int size() {
0757: return categories.length;
0758: }
0759:
0760: /**
0761: * Returns the element at the specified position in this list.
0762: */
0763: public final Object get(final int i) {
0764: return categories[i];
0765: }
0766:
0767: /**
0768: * Returns all categories in this {@code CategoryList}.
0769: */
0770: public final Object[] toArray() {
0771: return (Category[]) categories.clone();
0772: }
0773:
0774: /**
0775: * Returns a string representation of this category list.
0776: * The returned string is implementation dependent.
0777: * It is usually provided for debugging purposes only.
0778: */
0779: public final String toString() {
0780: return toString(this );
0781: }
0782:
0783: /**
0784: * Returns a string representation of this category list. The {@code owner}
0785: * argument allow for a different class name to be formatted.
0786: */
0787: final String toString(final Object owner) {
0788: final String lineSeparator = System.getProperty(
0789: "line.separator", "\n");
0790: StringBuffer buffer = new StringBuffer(Utilities
0791: .getShortClassName(owner));
0792: buffer = formatRange(buffer, null);
0793: if (hasGaps) {
0794: buffer.append(" with gaps");
0795: }
0796: buffer.append(lineSeparator);
0797: /*
0798: * Ecrit la liste des catégories en dessous.
0799: */
0800: for (int i = 0; i < categories.length; i++) {
0801: buffer.append(" ");
0802: buffer.append(categories[i] == main ? '*' : ' ');
0803: buffer.append(categories[i]);
0804: buffer.append(lineSeparator);
0805: }
0806: return buffer.toString();
0807: }
0808:
0809: /**
0810: * Compares the specified object with this category list for equality.
0811: * If the two objects are instances of {@link CategoryList}, then the
0812: * test is a little bit stricter than the default {@link AbstractList#equals}.
0813: */
0814: public boolean equals(final Object object) {
0815: if (object instanceof CategoryList) {
0816: final CategoryList that = (CategoryList) object;
0817: if (Arrays.equals(this .categories, that.categories)) {
0818: assert Arrays.equals(this .minimums, that.minimums);
0819: return Utilities.equals(this .overflowFallback,
0820: that.overflowFallback);
0821: }
0822: return false;
0823: }
0824: return (overflowFallback == null) && super .equals(object);
0825: }
0826:
0827: /**
0828: * Reset the {@link #last} field to a non-null value after deserialization.
0829: */
0830: private void readObject(final ObjectInputStream in)
0831: throws IOException, ClassNotFoundException {
0832: in.defaultReadObject();
0833: last = main;
0834: }
0835:
0836: ///////////////////////////////////////////////////////////////////////////////////////////////
0837: //////// ////////
0838: //////// I M P L E M E N T A T I O N O F MathTransform1D I N T E R F A C E ////////
0839: //////// ////////
0840: ///////////////////////////////////////////////////////////////////////////////////////////////
0841: /**
0842: * Gets the dimension of input points, which is 1.
0843: */
0844: public final int getSourceDimensions() {
0845: return 1;
0846: }
0847:
0848: /**
0849: * Gets the dimension of output points, which is 1.
0850: */
0851: public final int getTargetDimensions() {
0852: return 1;
0853: }
0854:
0855: /**
0856: * Tests whether this transform does not move any points.
0857: */
0858: public boolean isIdentity() {
0859: return false;
0860: }
0861:
0862: /**
0863: * Returns the inverse transform of this object.
0864: */
0865: public final MathTransform inverse() {
0866: return inverse;
0867: }
0868:
0869: /**
0870: * Ensure the specified point is one-dimensional.
0871: */
0872: private static void checkDimension(final DirectPosition point) {
0873: final int dim = point.getDimension();
0874: if (dim != 1) {
0875: throw new MismatchedDimensionException(Errors.format(
0876: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(1),
0877: new Integer(dim)));
0878: }
0879: }
0880:
0881: /**
0882: * Transforms the specified {@code ptSrc} and stores the result in {@code ptDst}.
0883: */
0884: public final DirectPosition transform(final DirectPosition ptSrc,
0885: DirectPosition ptDst) throws TransformException {
0886: checkDimension(ptSrc);
0887: if (ptDst == null) {
0888: ptDst = new GeneralDirectPosition(1);
0889: } else {
0890: checkDimension(ptDst);
0891: }
0892: ptDst.setOrdinate(0, transform(ptSrc.getOrdinate(0)));
0893: return ptDst;
0894: }
0895:
0896: /**
0897: * Gets the derivative of this transform at a point.
0898: */
0899: public final Matrix derivative(final DirectPosition point)
0900: throws TransformException {
0901: checkDimension(point);
0902: return new Matrix1(derivative(point.getOrdinate(0)));
0903: }
0904:
0905: /**
0906: * Gets the derivative of this function at a value.
0907: *
0908: * @param value The value where to evaluate the derivative.
0909: * @return The derivative at the specified point.
0910: * @throws TransformException if the derivative can't be evaluated at the specified point.
0911: */
0912: public final double derivative(final double value)
0913: throws TransformException {
0914: Category category = last;
0915: if (!(value >= category.minimum && value <= category.maximum)
0916: && Double.doubleToRawLongBits(value) != Double
0917: .doubleToRawLongBits(category.minimum)) {
0918: category = getCategory(value);
0919: if (category == null) {
0920: throw new TransformException(Errors.format(
0921: ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(
0922: value)));
0923: }
0924: last = category;
0925: }
0926: return category.transform.derivative(value);
0927: }
0928:
0929: /**
0930: * Transforms the specified value.
0931: *
0932: * @param value The value to transform.
0933: * @return the transformed value.
0934: * @throws TransformException if the value can't be transformed.
0935: */
0936: public final double transform(double value)
0937: throws TransformException {
0938: Category category = last;
0939: if (!(value >= category.minimum && value <= category.maximum)
0940: && Double.doubleToRawLongBits(value) != Double
0941: .doubleToRawLongBits(category.minimum)) {
0942: category = getCategory(value);
0943: if (category == null) {
0944: throw new TransformException(Errors.format(
0945: ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(
0946: value)));
0947: }
0948: last = category;
0949: }
0950: value = category.transform.transform(value);
0951: if (overflowFallback != null) {
0952: if (value < category.inverse.minimum)
0953: return category.inverse.minimum;
0954: if (value > category.inverse.maximum)
0955: return category.inverse.maximum;
0956: }
0957: assert category == inverse.getCategory(value).inverse : category;
0958: return value;
0959: }
0960:
0961: /**
0962: * Transforms a list of coordinate point ordinal values. This implementation can work on
0963: * either float or double arrays, since the quasi-totality of the implementation is the
0964: * same. Locale variables still {@code double} because this is the type used in
0965: * {@link Category} objects.
0966: *
0967: * @todo We could add an optimisation after the loops checking for category change:
0968: * if we were allowed to search for nearest category (overflowFallback!=null),
0969: * then make sure that the category really changed. There is already a slight
0970: * optimization for the most common cases, but maybe we could go a little bit
0971: * further.
0972: */
0973: private void transform(final double[] srcPts,
0974: final float[] srcFloat, int srcOff, final double[] dstPts,
0975: final float[] dstFloat, int dstOff, int numPts,
0976: final boolean doublePrecision) throws TransformException {
0977: final int srcToDst = dstOff - srcOff;
0978: Category category = last;
0979: double maximum = category.maximum;
0980: double minimum = category.minimum;
0981: long rawBits = Double.doubleToRawLongBits(minimum);
0982: final int direction;
0983: if (srcPts != dstPts || srcOff >= dstOff) {
0984: direction = +1;
0985: } else {
0986: direction = -1;
0987: dstOff += numPts - 1;
0988: srcOff += numPts - 1;
0989: }
0990: /*
0991: * Scan every points. Transforms will be performed by blocks, each time
0992: * the loop detects that the category has changed. The break point is near
0993: * the end of the loop, after we have done the transformation but before
0994: * to change category.
0995: */
0996: for (int peekOff = srcOff; true; peekOff += direction) {
0997: // NOTE: We do not need to setup 'value' since we are not going to use it if
0998: // numPts<0. Unfortunatly, the compiler flow analysis doesn't seem to
0999: // be sophesticated enough to detect this case. So we have to set a dummy
1000: // value in order to avoid compiler error.
1001: double value = 0;
1002: if (doublePrecision) { // Optimized loop for the 'double' version
1003: while (--numPts >= 0) {
1004: value = srcPts[peekOff];
1005: if ((value >= minimum && value <= maximum)
1006: || Double.doubleToRawLongBits(value) == rawBits) {
1007: peekOff += direction;
1008: continue;
1009: }
1010: break; // The category has changed. Stop the search.
1011: }
1012: } else {
1013: while (--numPts >= 0) { // Optimized loop for the 'float' version
1014: value = srcFloat[peekOff];
1015: if ((value >= minimum && value <= maximum)
1016: || Double.doubleToRawLongBits(value) == rawBits) {
1017: peekOff += direction;
1018: continue;
1019: }
1020: break; // The category has changed. Stop the search.
1021: }
1022: }
1023: if (overflowFallback != null) {
1024: // TODO: Slight optimization. We could go further by checking if 'value' is closer
1025: // to this category than to the previous category or the next category. But
1026: // we may need the category index, and binarySearch is a costly operation...
1027: if (value > maximum && category == overflowFallback) {
1028: continue;
1029: }
1030: if (value < minimum && category == categories[0]) {
1031: continue;
1032: }
1033: }
1034: /*
1035: * The category has changed. Compute the start point (which depends of 'direction')
1036: * and performs the transformation. If 'getCategory' was allowed to search for the
1037: * nearest category, clamp all output values in their category range.
1038: */
1039: int count = peekOff - srcOff; // May be negative if we are going backward.
1040: if (count < 0) {
1041: count = -count;
1042: srcOff -= count - 1;
1043: }
1044: if (doublePrecision) { // Optimized loop for the 'double' version.
1045: category.transform.transform(srcPts, srcOff, dstPts,
1046: srcOff + srcToDst, count);
1047: if (overflowFallback != null) {
1048: dstOff = srcOff + srcToDst;
1049: final double min = category.inverse.minimum;
1050: final double max = category.inverse.maximum;
1051: while (--count >= 0) { // Optimized loop for the 'double' version.
1052: final double check = dstPts[dstOff];
1053: if (check < min) {
1054: dstPts[dstOff] = min;
1055: } else if (check > max) {
1056: dstPts[dstOff] = max;
1057: }
1058: dstOff++;
1059: }
1060: }
1061: } else { // Optimized loop for the 'float' version.
1062: category.transform.transform(srcFloat, srcOff,
1063: dstFloat, srcOff + srcToDst, count);
1064: if (overflowFallback != null) {
1065: dstOff = srcOff + srcToDst;
1066: final float min = (float) category.inverse.minimum;
1067: final float max = (float) category.inverse.maximum;
1068: while (--count >= 0) { // Optimized loop for the 'double' version.
1069: final float check = dstFloat[dstOff];
1070: if (check < min) {
1071: dstFloat[dstOff] = min;
1072: } else if (check > max) {
1073: dstFloat[dstOff] = max;
1074: }
1075: dstOff++;
1076: }
1077: }
1078: }
1079: /*
1080: * Transformation is now finished for all points in the range [srcOff..peekOff]
1081: * (not including 'peekOff'). If there is more points to examine, gets the new
1082: * category for the next points.
1083: */
1084: if (numPts < 0) {
1085: break;
1086: }
1087: category = getCategory(value);
1088: if (category == null) {
1089: throw new TransformException(Errors.format(
1090: ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(
1091: value)));
1092: }
1093: maximum = category.maximum;
1094: minimum = category.minimum;
1095: rawBits = Double.doubleToRawLongBits(minimum);
1096: srcOff = peekOff;
1097: }
1098: last = category;
1099: }
1100:
1101: /**
1102: * Transforms a list of coordinate point ordinal values.
1103: */
1104: public final void transform(double[] srcPts, int srcOff,
1105: double[] dstPts, int dstOff, int numPts)
1106: throws TransformException {
1107: transform(srcPts, null, srcOff, dstPts, null, dstOff, numPts,
1108: true);
1109: }
1110:
1111: /**
1112: * Transforms a list of coordinate point ordinal values.
1113: */
1114: public final void transform(float[] srcPts, int srcOff,
1115: float[] dstPts, int dstOff, int numPts)
1116: throws TransformException {
1117: transform(null, srcPts, srcOff, null, dstPts, dstOff, numPts,
1118: false);
1119: }
1120:
1121: /**
1122: * Transform a raster. Only the current band in {@code iterator} will be transformed.
1123: * The transformed value are write back in the {@code iterator}. If a different
1124: * destination raster is wanted, a {@link org.geotools.image.TransfertRectIter}
1125: * may be used.
1126: *
1127: * @param iterator An iterator to iterate among the samples to transform.
1128: * @throws RasterFormatException if a problem occurs during the transformation.
1129: */
1130: public final void transform(final WritableRectIter iterator)
1131: throws RasterFormatException {
1132: /*
1133: * Category of the lowest minimum and highest maximum value (not including NaN),
1134: * or <code>null</code in none. Will be used later for range checks.
1135: */
1136: Category categoryMin = null, categoryMax = null;
1137: for (int i = categories.length; --i >= 0;) {
1138: if (!Double.isNaN(categories[i].maximum)) {
1139: categoryMax = categories[i];
1140: categoryMin = categories[0];
1141: break;
1142: }
1143: }
1144: Category category = main;
1145: if (main == null) {
1146: category = nodata;
1147: }
1148: double maximum = category.maximum;
1149: double minimum = category.minimum;
1150: long rawBits = Double.doubleToRawLongBits(minimum);
1151: MathTransform1D tr = category.transform;
1152: double maxTr, minTr;
1153: if (overflowFallback == null) {
1154: maxTr = Double.POSITIVE_INFINITY;
1155: minTr = Double.NEGATIVE_INFINITY;
1156: } else {
1157: maxTr = category.inverse.maximum;
1158: minTr = category.inverse.minimum;
1159: }
1160: try {
1161: iterator.startLines();
1162: if (!iterator.finishedLines())
1163: do {
1164: iterator.startPixels();
1165: if (!iterator.finishedPixels())
1166: do {
1167: double value = iterator.getSampleDouble();
1168: if (!(value >= minimum && value <= maximum)
1169: && // 'true' if value is NaN...
1170: Double.doubleToRawLongBits(value) != rawBits) // and the NaN bits changed.
1171: {
1172: // Category has changed. Find the new category.
1173: category = getCategory(value);
1174: if (category == null) {
1175: category = nodata;
1176: }
1177: maximum = (category != categoryMax) ? category.maximum
1178: : Double.POSITIVE_INFINITY;
1179: minimum = (category != categoryMin) ? category.minimum
1180: : Double.NEGATIVE_INFINITY;
1181: rawBits = Double
1182: .doubleToRawLongBits(minimum);
1183: tr = category.transform;
1184: if (overflowFallback != null) {
1185: maxTr = category.inverse.maximum;
1186: minTr = category.inverse.minimum;
1187: }
1188: }
1189: /*
1190: * TODO: This assertion fails in some circonstance: during conversions from
1191: * geophysics to sample values and when the sample value is outside
1192: * the inclusive range but inside the exclusive range... In this case
1193: * 'getCategory(double)' may choose the wrong category. The fix would
1194: * be to add new fiels in Category: we should have 'minInclusive' and
1195: * 'minExclusive' instead of just 'minimum', and same for 'maximum'.
1196: * The CategoryList.minimums array would still inclusive, but tests
1197: * for range inclusion should use the exclusive extremas.
1198: */
1199: assert hasGaps
1200: || (category == nodata)
1201: || // Disable assertion in those cases
1202: (Double.isNaN(value) ? Double
1203: .doubleToRawLongBits(value) == rawBits
1204: : (value >= minimum && value <= maximum)) : value;
1205: value = tr.transform(value);
1206: if (value > maxTr) {
1207: value = maxTr;
1208: } else if (value < minTr) {
1209: value = minTr;
1210: }
1211: iterator.setSample(value);
1212: } while (!iterator.nextPixelDone());
1213: } while (!iterator.nextLineDone());
1214: } catch (TransformException cause) {
1215: RasterFormatException exception = new RasterFormatException(
1216: Errors.format(ErrorKeys.BAD_TRANSFORM_$1, Utilities
1217: .getShortClassName(tr)));
1218: exception.initCause(cause);
1219: throw exception;
1220: }
1221: }
1222:
1223: /**
1224: * Returns a Well Known Text</cite> (WKT) for this object. This operation
1225: * may fails if an object is too complex for the WKT format capability.
1226: *
1227: * @return The Well Know Text for this object.
1228: * @throws UnsupportedOperationException If this object can't be formatted as WKT.
1229: *
1230: * @todo Not yet implemented.
1231: */
1232: public String toWKT() throws UnsupportedOperationException {
1233: throw new UnformattableObjectException("Not yet implemented.",
1234: getClass());
1235: }
1236: }
|