001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Management Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.coverage;
021:
022: // J2SE dependencies and extensions
023: import java.text.FieldPosition;
024: import java.text.NumberFormat;
025: import java.util.AbstractList;
026: import java.util.Locale;
027: import javax.units.Unit;
028:
029: // Geotools dependencies
030: import org.geotools.resources.Utilities;
031: import org.geotools.resources.XMath;
032:
033: /**
034: * An immutable list of geophysics category. Elements are usually (but not always) instances
035: * of [@link GeophysicsCategory}. Exception to this rule includes categories wrapping an
036: * identity transforms.
037: *
038: * This list can transform geophysics values into sample values using
039: * the list of {@link Category}. This transform is thread safe if each
040: * {@link Category#getSampleToGeophysics} transform is thread-safe too.
041: *
042: * @since 2.1
043: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/GeophysicsCategoryList.java $
044: * @version $Id: GeophysicsCategoryList.java 22817 2006-11-17 17:24:55Z desruisseaux $
045: * @author Martin Desruisseaux
046: */
047: final class GeophysicsCategoryList extends CategoryList {
048: /**
049: * Serial number for interoperability with different versions.
050: */
051: private static final long serialVersionUID = 98602310176453958L;
052:
053: /**
054: * Maximum value for {@link #ndigits}. This is the number of
055: * significant digits to allow when formatting a geophysics value.
056: */
057: private static final int MAX_DIGITS = 6;
058:
059: /**
060: * Unités des mesures géophysiques représentées par les catégories.
061: * Ce champ peut être nul s'il ne s'applique pas ou si les unités
062: * ne sont pas connues.
063: */
064: private final Unit unit;
065:
066: /**
067: * Nombre de chiffres significatifs après la virgule.
068: * Cette information est utilisée pour les écritures
069: * des valeurs géophysiques des catégories.
070: */
071: private final int ndigits;
072:
073: /**
074: * Locale used for creating {@link #format} last time.
075: * May be {@code null} if default locale was requested.
076: */
077: private transient Locale locale;
078:
079: /**
080: * Format à utiliser pour écrire les
081: * valeurs géophysiques des thèmes.
082: */
083: private transient NumberFormat format;
084:
085: /**
086: * Objet temporaire pour {@link NumberFormat}.
087: */
088: private transient FieldPosition dummy;
089:
090: /**
091: * Constructs a category list using the specified array of categories.
092: *
093: * @param categories The list of categories. Elements should be
094: * instances of {@link GeophysicsCategory}
095: * (most of the time, but not always).
096: * @param unit The unit information for all quantitative categories.
097: * May be {@code null} if no category has units.
098: * @param inverse The {@link CategoryList} which is constructing this
099: * {@link GeophysicsCategoryList}.
100: *
101: * @throws IllegalArgumentException if two or more categories
102: * have overlapping sample value range.
103: */
104: GeophysicsCategoryList(Category[] categories, final Unit unit,
105: final CategoryList inverse) {
106: super (categories, unit, true, inverse);
107: this .unit = unit;
108: this .ndigits = getFractionDigitCount(categories);
109: assert isScaled(true);
110: }
111:
112: /**
113: * Computes the smallest number of fraction digits necessary to resolve all
114: * quantitative values. This method assume that geophysics values in the range
115: * {@code Category.geophysics(true).getRange} are stored as integer sample
116: * values in the range {@code Category.geophysics(false).getRange}.
117: */
118: private static int getFractionDigitCount(final Category[] categories) {
119: int ndigits = 0;
120: final double EPS = 1E-6;
121: final int length = categories.length;
122: for (int i = 0; i < length; i++) {
123: final Category geophysics = categories[i].geophysics(true);
124: final Category samples = categories[i].geophysics(false);
125: final double ln = XMath
126: .log10((geophysics.maximum - geophysics.minimum)
127: / (samples.maximum - samples.minimum));
128: if (!Double.isNaN(ln)) {
129: final int n = -(int) (Math.floor(ln + EPS));
130: if (n > ndigits) {
131: ndigits = Math.min(n, MAX_DIGITS);
132: }
133: }
134: }
135: return ndigits;
136: }
137:
138: /**
139: * If {@code toGeophysics} is {@code false}, cancel the action of a previous
140: * call to {@code geophysics(true)}. This method always returns a list of categories
141: * in which <code>{@link Category#geophysics(boolean) Category.geophysics}(toGeophysics)</code>
142: * has been invoked for each category.
143: */
144: public CategoryList geophysics(final boolean toGeophysics) {
145: final CategoryList scaled = toGeophysics ? this : inverse;
146: assert scaled.isScaled(toGeophysics);
147: return scaled;
148: }
149:
150: /**
151: * Returns the unit information for quantitative categories in this list.
152: * May returns {@code null} if there is no quantitative categories
153: * in this list, or if there is no unit information.
154: */
155: public Unit getUnits() {
156: return unit;
157: }
158:
159: /**
160: * Formatte la valeur spécifiée selon les conventions locales. Le nombre sera
161: * écrit avec un nombre de chiffres après la virgule approprié pour la catégorie.
162: * Le symbole des unités sera ajouté après le nombre si {@code writeUnit}
163: * est {@code true}.
164: *
165: * @param value Valeur du paramètre géophysique à formatter.
166: * @param writeUnit Indique s'il faut écrire le symbole des unités après le nombre.
167: * Cet argument sera ignoré si aucune unité n'avait été spécifiée au constructeur.
168: * @param locale Conventions locales à utiliser, ou {@code null} pour les conventions par
169: * défaut.
170: * @param buffer Le buffer dans lequel écrire la valeur.
171: * @return Le buffer {@code buffer} dans lequel auront été écrit la valeur et les unités.
172: */
173: synchronized StringBuffer format(final double value,
174: final boolean writeUnits, final Locale locale,
175: StringBuffer buffer) {
176: if (format == null || !Utilities.equals(this .locale, locale)) {
177: this .locale = locale;
178: format = (locale != null) ? NumberFormat
179: .getNumberInstance(locale) : NumberFormat
180: .getNumberInstance();
181: format.setMinimumFractionDigits(ndigits);
182: format.setMaximumFractionDigits(ndigits);
183: dummy = new FieldPosition(0);
184: }
185: buffer = format.format(value, buffer, dummy);
186: if (writeUnits && unit != null) {
187: final int position = buffer.length();
188: buffer.append('\u00A0'); // No-break space
189: buffer.append(unit);
190: if (buffer.length() == position + 1) {
191: buffer.setLength(position);
192: }
193: }
194: return buffer;
195: }
196:
197: /**
198: * Compares the specified object with this category list for equality.
199: * If the two objects are instances of {@link CategoryList}, then the
200: * test is a little bit stricter than the default {@link AbstractList#equals}.
201: */
202: public boolean equals(final Object object) {
203: if (object instanceof GeophysicsCategoryList) {
204: final GeophysicsCategoryList that = (GeophysicsCategoryList) object;
205: return this .ndigits == that.ndigits
206: && Utilities.equals(this .unit, that.unit)
207: && super .equals(that);
208: }
209: return ndigits == 0 && unit == null && super.equals(object);
210: }
211: }
|