/* * 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.image.io.text; import java.util.Arrays; import javax.imageio.IIOException; import org.geotools.resources.Classes; import org.geotools.resources.XArray; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; /** * List of data records in an image. One instance of this class is created by * {@link TextRecordImageReader} for every image in a file. A {@code RecordList} * contains a list of records where each record contains data for one pixel. A record * contains usually the following information: * * <ul> * <li>Pixel's x and y coordinate.</li> * <li>Pixel's values for each band.</li> * </li> * * Those information can appear in arbitrary columns, providing that the column order * stay the same for every record in a particular {@code RecordList} instance. * Records can appear in arbitrary order. * <p> * Data can be floating point value ({@code float} type). Current implementation * expects pixels distributed on a regular grid. The grid interval will be automatically * computed when needed. The interval computation should be accurate even if there is * missing and/or duplicated records. * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ final class RecordList { /** * Valeurs minimales des colonnes, ou {@code null} si * ces valeurs ne sont pas encore connues. La longueur de * de ce tableau est égale à {@link #dataColumnCount}. */ private double[] min; /** * Valeurs maximales des colonnes, ou {@code null} si * ces valeurs ne sont pas encore connues. La longueur de * de ce tableau est égale à {@link #dataColumnCount}. */ private double[] max; /** * Intervals entre les données, ou {@code null} si ces valeurs * n'ont pas encore été calculées. La valeur 0 signifie que l'interval * pour une colonne en particulier n'a pas encore été calculée. */ private float[] interval; /** * Tableau des valeurs lues, ou {@code null} si les * valeurs n'ont pas encore été lues. Ce tableau contient * une suite de lignes qui ont chacun un nombre de colonnes * égal à {@link #dataColumnCount}. */ private float[] data; /** * Nombre de colonnes retenues lors de la lecture, ou -1 si ce nombre * n'est pas encore connu. Ce nombre de colonnes peut être égal ou * inférieur à {@code min.length} et {@code max.length}. */ private int columnCount = -1; /** * Index suivant celui du dernier élément valide de {@link #data}. * Ce champ sera augmenté à chaque ajout d'une nouvelle ligne. Sa * valeur doit être un multiple entier de {@link #dataColumnCount}. */ private int upper; /** * Nombre de lignes attendues. Cette information n'est qu'à titre * indicative, mais accelerera la lecture si elle est exacte. */ private int expectedLineCount = 1024; /** * Construit un {@code ImageData} initiallement vide. * La première ligne de données lue déterminera le nombre * de colonnes qui seront retenus pour toutes les lignes * suivantes. */ public RecordList() { } /** * Construit un {@code ImageData} initiallement vide. * Pour chaque ligne lue, seule les {@code columnCount} * premières colonnes seront retenus. * * @param columnCount Nombre de colonnes à retenir lors de la lecture. * @param expectedLineCount Nombre de lignes attendues. Cette information * n'est qu'à titre indicative, mais accelerera la lecture si elle * est exacte. */ public RecordList(final int columnCount, final int expectedLineCount) { this.columnCount = columnCount; this.expectedLineCount = expectedLineCount; } /** * Ajoute une ligne de données. Si la ligne est plus courte que la longueur * attendues, les colonnes manquantes seront considérées comme contenant des * {@code NaN}. Si elle est plus longue que la longueur attendue, les * colonnes en trop seront ignorées. */ public void add(final double[] line) { if (data==null) { if (columnCount<0) columnCount=line.length; min = new double[columnCount]; Arrays.fill(min, Double.POSITIVE_INFINITY); max = new double[columnCount]; Arrays.fill(max, Double.NEGATIVE_INFINITY); data = new float [columnCount*expectedLineCount]; } final int limit=Math.min(columnCount, line.length); final int nextUpper = upper+columnCount; if (nextUpper >= data.length) { data = XArray.resize(data, Math.max(nextUpper, data.length+Math.min(data.length, 65536))); } for (int i=0; i<limit; i++) { final double value = line[i]; if (value < min[i]) min[i] = value; if (value > max[i]) max[i] = value; data[upper+i] = (float)value; } Arrays.fill(data, upper+limit, nextUpper, Float.NaN); upper = nextUpper; } /** * Libère la mémoire réservée en trop. Cette méthode peut être appelée * lorsqu'on a terminé de lire les données et qu'on veut les conserver * en mémoire pendant encore quelque temps. */ public void trimToSize() { if (data != null) { data = XArray.resize(data, upper); } } /** * Retourne une référence directe vers les données mémorisées par cet objet. * NE PAS MODIFIER CES DONNEES! Les index valides vont de 0 inclusivement * jusqu'à {@link #getDataCount} exclusivement. */ final float[] getData() { return data; } /** * Retourne le nombre de données qui ont été mémorisées. */ final int getDataCount() { return upper; } /** * Retourne le nombre de lignes qui ont été mémorisées. */ public int getLineCount() { if (columnCount <= 0) { return 0; } assert (upper % columnCount) == 0; return upper / columnCount; } /** * Retourne le nombre de colonnes, ou * -1 si ce nombre n'est pas connu. */ public int getColumnCount() { return columnCount; } /** * Retourne la valeur minimale de la colonne spécifiée, * ou {@link Double#NaN} si cette valeur n'est pas connue. */ public double getMinimum(final int column) { return (min != null && min[column] <= max[column]) ? min[column] : Double.NaN; } /** * Retourne la valeur maximale de la colonne spécifiée, * ou {@link Double#NaN} si cette valeur n'est pas connue. */ public double getMaximum(final int column) { return (max != null && max[column] >= min[column]) ? max[column] : Double.NaN; } /** * Retourne l'interval entre les points de la colonne spécifiée, en supposant que les * points se trouvent à un interval régulier. Si ce n'est pas le cas, une exception * sera lancée. * * @param column Colonne dont on veut l'interval entre les points. * @param eps Petit facteur de tolérance (par exemple 1E-6). * @throws IIOException si les points de la colonne spécifiée * ne sont pas distribués à un interval régulier. */ private float getInterval(final int column, final float eps) throws IIOException { if (interval == null) { if (columnCount <= 0) { return Float.NaN; } interval = new float[columnCount]; } if (interval[column] != 0) { return interval[column]; } /* * Obtient toutes les valeurs de la colonne * spécifiée en ordre croissant. */ int count=0; final float[] array = new float[getLineCount()]; for (int i=column; i<upper; i+=columnCount) { array[count++] = data[i]; } assert count == array.length; Arrays.sort(array); /* * Elimine les doublons. Lorsque des doublons seront trouvés, ils iront de * {@code lower} à {@code upper} <strong>inclusivement</strong>. */ int upper = count-1; int lower = count; while (--lower>=1) { if (array[upper] != array[lower-1]) { if (upper != lower) { System.arraycopy(array, upper, array, lower, count-upper); final int oldCount = count; count -= (upper-lower); Arrays.fill(array, count, oldCount, Float.NaN); // Par prudence. } upper = lower-1; } } if (upper != lower) { System.arraycopy(array, upper, array, lower, count-upper); final int oldCount = count; count -= (upper-lower); Arrays.fill(array, count, oldCount, Float.NaN); // Par prudence. } /* * Recherche le plus petit interval entre deux points. Vérifie ensuite que * l'interval entre tous les points est un multiple entier de cet interval * minimal (on tient compte ainsi des éventuels données manquantes). */ float delta = Float.POSITIVE_INFINITY; for (int i=1; i<count; i++) { final float d = array[i] - array[i-1]; assert d>0; if (d < delta) { delta = d; } } for (int i=1; i<count; i++) { float e = (array[i] - array[i-1]) / delta; if (Math.abs(e-Math.rint(e)) > eps) { throw new IIOException(Errors.format(ErrorKeys.NOT_A_GRID)); } } return interval[column] = Float.isInfinite(delta) ? Float.NaN : delta; } /** * Retourne le nombre de points distincts dans la colonne spécifiée. Cette méthode * élimine d'abord tous les doublons avant d'effectuer le comptage. Elle vérifie * aussi que les points restants sont espacés à un interval régulier, et lancera * une exception si ce n'est pas le cas. S'il y a des trous dans les données, il * seront pris en compte comme si un point s'y était trouvé. * * @param column Colonne dont on veut le nombre de points distincts. * @param eps Petit facteur de tolérance (par exemple 1E-6). * @throws IIOException si les points de la colonne spécifiée * ne sont pas distribués à un interval régulier. */ public int getPointCount(final int column, final float eps) throws IIOException { return (int)Math.round((getMaximum(column) - getMinimum(column)) / getInterval(column, eps)) +1; } /** * Retourne un résumé des informations que contient cet objet. Le résumé contiendra * notamment les valeurs minimales et maximales de chaque colonnes. * * @param xColumn Colonne des <var>x</var>, ou -1 s'il n'est pas connu. * @param yColumn Colonne des <var>y</var>, ou -1 s'il n'est pas connu. * @param eps Petit facteur de tolérance (par exemple 1E-6). * @return Chaîne de caractères résumant l'état des données. */ public String toString(final int xColumn, final int yColumn, final float eps) { float xCount = Float.NaN; float yCount = Float.NaN; if (xColumn >= 0) try { xCount = getPointCount(xColumn, eps); } catch (IIOException exception) { // Ignore. } if (yColumn >= 0) try { yCount = getPointCount(yColumn, eps); } catch (IIOException exception) { // Ignore. } return Vocabulary.format(VocabularyKeys.POINT_COUNT_IN_GRID_$3, upper, xCount, yCount); } /** * Retourne une chaîne de caractères représentant cet objet. * Cette chaîne indiquera le nombre de lignes et de colonnes * mémorisées. */ @Override public String toString() { return Classes.getShortClassName(this) + '[' + getLineCount() + "\u00A0\u00D7\u00A0" + getColumnCount() + ']'; } }