/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-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.coverage;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Locale;
import javax.measure.unit.Unit;
import org.geotools.util.Utilities;
/**
* An immutable list of geophysics category. Elements are usually (but not always) instances
* of [@link GeophysicsCategory}. Exception to this rule includes categories wrapping an
* identity transforms.
*
* This list can transform geophysics values into sample values using
* the list of {@link Category}. This transform is thread safe if each
* {@link Category#getSampleToGeophysics} transform is thread-safe too.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
final class GeophysicsCategoryList extends CategoryList {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 98602310176453958L;
/**
* Maximum value for {@link #ndigits}. This is the number of
* significant digits to allow when formatting a geophysics value.
*/
private static final int MAX_DIGITS = 6;
/**
* Unités des mesures géophysiques représentées par les catégories.
* Ce champ peut être nul s'il ne s'applique pas ou si les unités
* ne sont pas connues.
*/
private final Unit<?> unit;
/**
* Nombre de chiffres significatifs après la virgule.
* Cette information est utilisée pour les écritures
* des valeurs géophysiques des catégories.
*/
private final int ndigits;
/**
* Locale used for creating {@link #format} last time.
* May be {@code null} if default locale was requested.
*/
private transient Locale locale;
/**
* Format à utiliser pour écrire les
* valeurs géophysiques des thèmes.
*/
private transient NumberFormat format;
/**
* Objet temporaire pour {@link NumberFormat}.
*/
private transient FieldPosition dummy;
/**
* Constructs a category list using the specified array of categories.
*
* @param categories The list of categories. Elements should be
* instances of {@link GeophysicsCategory}
* (most of the time, but not always).
* @param unit The unit information for all quantitative categories.
* May be {@code null} if no category has units.
* @param inverse The {@link CategoryList} which is constructing this
* {@link GeophysicsCategoryList}.
*
* @throws IllegalArgumentException if two or more categories
* have overlapping sample value range.
*/
GeophysicsCategoryList(Category[] categories, final Unit<?> unit, final CategoryList inverse) {
super(categories, unit, true, inverse);
this.unit = unit;
this.ndigits = getFractionDigitCount(categories);
assert isGeophysics(true);
}
/**
* Computes the smallest number of fraction digits necessary to resolve all
* quantitative values. This method assume that geophysics values in the range
* {@code Category.geophysics(true).getRange} are stored as integer sample
* values in the range {@code Category.geophysics(false).getRange}.
*/
private static int getFractionDigitCount(final Category[] categories) {
int ndigits = 0;
final double EPS = 1E-6;
final int length=categories.length;
for (int i=0; i<length; i++) {
final Category category = categories[i];
final Category geophysics = category.geophysics(true);
final Category packed = category.geophysics(false);
final double ln = Math.log10((geophysics.maximum - geophysics.minimum)/
( packed.maximum - packed.minimum));
if (!Double.isNaN(ln)) {
final int n = -(int)(Math.floor(ln + EPS));
if (n > ndigits) {
ndigits = Math.min(n, MAX_DIGITS);
}
}
}
return ndigits;
}
/**
* If {@code geo} is {@code false}, cancel the action of a previous call to
* {@code geophysics(true)}. This method always returns a list of categories in which
* <code>{@linkplain Category#geophysics(boolean) Category.geophysics}(geo)</code>
* has been invoked for each category.
*/
@Override
public CategoryList geophysics(final boolean geo) {
final CategoryList scaled = geo ? this : inverse;
assert scaled.isGeophysics(geo);
return scaled;
}
/**
* Returns the unit information for quantitative categories in this list.
* May returns {@code null} if there is no quantitative categories
* in this list, or if there is no unit information.
*/
@Override
public Unit<?> getUnits() {
return unit;
}
/**
* Formatte la valeur spécifiée selon les conventions locales. Le nombre sera
* écrit avec un nombre de chiffres après la virgule approprié pour la catégorie.
* Le symbole des unités sera ajouté après le nombre si {@code writeUnit}
* est {@code true}.
*
* @param value Valeur du paramètre géophysique à formatter.
* @param writeUnit Indique s'il faut écrire le symbole des unités après le nombre.
* Cet argument sera ignoré si aucune unité n'avait été spécifiée au constructeur.
* @param locale Conventions locales à utiliser, ou {@code null} pour les conventions par
* défaut.
* @param buffer Le buffer dans lequel écrire la valeur.
* @return Le buffer {@code buffer} dans lequel auront été écrit la valeur et les unités.
*/
@Override
synchronized StringBuffer format(final double value, final boolean writeUnits,
final Locale locale, StringBuffer buffer)
{
if (format==null || !Utilities.equals(this.locale, locale)) {
this.locale = locale;
format=(locale!=null) ? NumberFormat.getNumberInstance(locale) :
NumberFormat.getNumberInstance();
format.setMinimumFractionDigits(ndigits);
format.setMaximumFractionDigits(ndigits);
dummy = new FieldPosition(0);
}
buffer = format.format(value, buffer, dummy);
if (writeUnits && unit!=null) {
final int position = buffer.length();
buffer.append('\u00A0'); // No-break space
buffer.append(unit);
if (buffer.length() == position+1) {
buffer.setLength(position);
}
}
return buffer;
}
/**
* Compares the specified object with this category list for equality.
* If the two objects are instances of {@link CategoryList}, then the
* test is a little bit stricter than the default {@link AbstractList#equals}.
*/
@Override
public boolean equals(final Object object) {
if (object instanceof GeophysicsCategoryList) {
final GeophysicsCategoryList that = (GeophysicsCategoryList) object;
return this.ndigits == that.ndigits &&
Utilities.equals(this.unit, that.unit) &&
super.equals(that);
}
return ndigits==0 && unit==null && super.equals(object);
}
}