/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2001-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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.geotoolkit.coverage; import java.text.FieldPosition; import java.text.NumberFormat; import java.util.Locale; import java.util.Objects; import javax.measure.Unit; /** * 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. * <p> * 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. * * @author Martin Desruisseaux (IRD) * @version 3.00 * * @since 2.0 * @module */ final class GeophysicsCategoryList extends CategoryList { /** * Serial number for inter-operability 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; /** * Workaround for rounding errors. */ private static final double EPS = 1E-6; /** * Units of measurement of geophysical values, or {@code null} if not applicable. */ private final Unit<?> unit; /** * Number of significant digits, used for formatting geophysical values. */ 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 to use for writing geophysical values. */ private transient NumberFormat format; /** * Constructs a category list using the specified array of categories. * * @param categories * The list of categories. Elements should be instances of {@code 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 {@code 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 assumes 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 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 || !Objects.equals(this.locale, locale)) { this.locale = locale; format = (locale != null) ? NumberFormat.getNumberInstance(locale) : NumberFormat.getNumberInstance(); format.setMinimumFractionDigits(ndigits); format.setMaximumFractionDigits(ndigits); } buffer = format.format(value, buffer, new FieldPosition(0)); if (writeUnits && unit!=null) { final int position = buffer.length(); buffer.append('\u00A0').append(unit); // No-break space 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 && Objects.equals(this.unit, that.unit) && super.equals(that); } return ndigits == 0 && unit == null && super.equals(object); } }