001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment 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: package org.geotools.image.io.text;
018:
019: import java.awt.Point;
020: import java.awt.Rectangle;
021: import java.awt.geom.AffineTransform;
022: import java.awt.image.BufferedImage;
023: import java.awt.image.DataBuffer;
024: import java.awt.image.WritableRaster;
025: import java.io.BufferedReader;
026: import java.io.IOException;
027: import java.text.ParseException;
028: import java.util.ArrayList;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Locale;
032: import javax.imageio.IIOException;
033: import javax.imageio.ImageReadParam;
034: import javax.imageio.ImageReader;
035: import javax.imageio.ImageTypeSpecifier;
036: import javax.imageio.metadata.IIOMetadata;
037: import javax.imageio.spi.ImageReaderSpi;
038:
039: import org.geotools.io.LineFormat;
040: import org.geotools.resources.XArray;
041: import org.geotools.resources.i18n.Errors;
042: import org.geotools.resources.i18n.ErrorKeys;
043: import org.geotools.resources.i18n.Descriptions;
044: import org.geotools.resources.i18n.DescriptionKeys;
045: import org.geotools.image.io.metadata.ImageGeometry;
046: import org.geotools.image.io.metadata.GeographicMetadata;
047:
048: /**
049: * Image decoder for text files storing pixel values as records.
050: * Such text files use one line (record) by pixel. Each line contains
051: * at least 3 columns (in arbitrary order):
052: *
053: * <ul>
054: * <li>Pixel's <var>x</var> coordinate.</li>
055: * <li>Pixel's <var>y</var> coordinate.</li>
056: * <li>An arbitrary number of pixel values.</li>
057: * </ul>
058: *
059: * For example, some Sea Level Anomaly (SLA) files contains rows of longitude
060: * (degrees), latitude (degrees), SLA (cm), East/West current (cm/s) and
061: * North/South current (cm/s), as below:
062: *
063: * <blockquote><pre>
064: * 45.1250 -29.8750 -7.28 10.3483 -0.3164
065: * 45.1250 -29.6250 -4.97 11.8847 3.6192
066: * 45.1250 -29.3750 -2.91 3.7900 3.0858
067: * 45.1250 -29.1250 -3.48 -5.1833 -5.0759
068: * 45.1250 -28.8750 -4.36 -1.8129 -16.3689
069: * 45.1250 -28.6250 -3.91 7.5577 -24.6801
070: * </pre>(...etc...)
071: * </blockquote>
072: *
073: * From this decoder point of view, the two first columns (longitude and latitude)
074: * are pixel's logical coordinate (<var>x</var>,<var>y</var>), while the three last
075: * columns are three image's bands. The whole file contains only one image (unless
076: * {@link #getNumImages} has been overridden). All (<var>x</var>,<var>y</var>)
077: * coordinates belong to pixel's center. This decoder will automatically translate
078: * (<var>x</var>,<var>y</var>) coordinates from logical space to pixel space. The
079: * {@link #getTransform} method provides a convenient {@link AffineTransform} for
080: * performing coordinate transformations between pixel and logical spaces.
081: * <p>
082: * By default, {@code TextRecordImageReader} assumes that <var>x</var> and
083: * <var>y</var> coordinates appear in column #0 and 1 respectively. It also assumes
084: * that numeric values are encoded using current defaults {@link java.nio.charset.Charset}
085: * and {@link java.util.Locale}, and that there is no pad value. The easiest way to change
086: * the default setting is to create a {@link Spi} subclass. There is no need to subclass
087: * {@code TextRecordImageReader}, unless you want more control on the decoding process.
088: *
089: * @since 2.1
090: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextRecordImageReader.java $
091: * @version $Id: TextRecordImageReader.java 27629 2007-10-26 09:59:20Z desruisseaux $
092: * @author Martin Desruisseaux
093: */
094: public class TextRecordImageReader extends TextImageReader {
095: /**
096: * Petit facteur de tolérance servant à tenir compte des erreurs d'arrondissement.
097: */
098: private static final float EPS = 1E-5f;
099:
100: /**
101: * Intervalle (en nombre d'octets) entre les rapports de progrès.
102: */
103: private static final int PROGRESS_INTERVAL = 4096;
104:
105: /**
106: * Lorsque la lecture se fait par-dessus une image {@link BufferedReader} existante,
107: * indique s'il faut effacer la région dans laquelle sera placée l'image avant de la
108: * lire. La valeur {@code false} permettra de conserver les anciens pixels dans
109: * les régions ou le fichier ne définit pas de nouvelles valeurs.
110: */
111: private static final boolean CLEAR = true;
112:
113: /**
114: * Données des images, ou {@code null} si aucune lecture n'a encore été
115: * faite. Chaque élément contient les données de l'image à l'index correspondant
116: * (i.e. l'élément {@code data[0]} contient les données de l'image #0,
117: * {@code data[1]} contient les données de l'image #1, etc.). Des éléments
118: * de ce tableau peuvent être nuls si les données des images correspondantes
119: * ne sont pas retenues après chaque lecture (c'est-à-dire si
120: * <code>{@link #seekForwardOnly}==true</code>).
121: */
122: private RecordList[] data;
123:
124: /**
125: * Index de la prochaine image à lire. Cet index n'est pas nécessairement
126: * égal à la longueur du tableau {@link #data}. Il peut être aussi bien
127: * plus petit que plus grand.
128: */
129: private int nextImageIndex;
130:
131: /**
132: * Nombre moyen de caractères par données (incluant les espaces et les codes
133: * de fin de ligne). Cette information n'est qu'à titre indicative, mais son
134: * exactitude peut aider à accelerer la lecture et rendre les rapport des
135: * progrès plus précis. Elle sera automatiquement mise à jour en fonction
136: * des lignes lues.
137: */
138: private float expectedDatumLength = 10.4f;
139:
140: /**
141: * Constructs a new image reader.
142: *
143: * @param provider the provider that is invoking this constructor, or {@code null} if none.
144: */
145: public TextRecordImageReader(final ImageReaderSpi provider) {
146: super (provider);
147: }
148:
149: /**
150: * Returns the grid tolerance (epsilon) value.
151: */
152: private float getGridTolerance() {
153: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).gridTolerance
154: : EPS;
155: }
156:
157: /**
158: * Returns the column number for <var>x</var> values. The default implementation returns
159: * {@link TextRecordImageReader.Spi#xColumn}. Subclasses should override this method if
160: * this information should be obtained in an other way.
161: *
162: * @param imageIndex The index of the image to be queried.
163: * @throws IOException If an error occurs reading the from the input source.
164: */
165: protected int getColumnX(final int imageIndex) throws IOException {
166: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).xColumn
167: : 0;
168: }
169:
170: /**
171: * Invokes {@link #getColumnX} and checks the result.
172: */
173: private int getCheckedColumnX(final int imageIndex)
174: throws IOException {
175: final int xColumn = getColumnX(imageIndex);
176: if (xColumn < 0) {
177: throw new IllegalStateException(Errors.format(
178: ErrorKeys.NEGATIVE_COLUMN_$2, "x", new Integer(
179: xColumn)));
180: }
181: return xColumn;
182: }
183:
184: /**
185: * Returns the column number for <var>x</var> values. The default implementation returns
186: * {@link TextRecordImageReader.Spi#yColumn}. Subclasses should override this method if
187: * this information should be obtained in an other way.
188: *
189: * @param imageIndex The index of the image to be queried.
190: * @throws IOException If an error occurs reading the from the input source.
191: */
192: protected int getColumnY(final int imageIndex) throws IOException {
193: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).yColumn
194: : 1;
195: }
196:
197: /**
198: * Invokes {@link #getColumnY} and checks the result.
199: */
200: private int getCheckedColumnY(final int imageIndex)
201: throws IOException {
202: final int yColumn = getColumnY(imageIndex);
203: if (yColumn < 0) {
204: throw new IllegalStateException(Errors.format(
205: ErrorKeys.NEGATIVE_COLUMN_$2, "y", new Integer(
206: yColumn)));
207: }
208: return yColumn;
209: }
210:
211: /**
212: * Retourne le numéro de colonne dans laquelle se trouvent les données de la
213: * bande spécifiée. L'implémentation par défaut retourne {@code band}+1
214: * ou 2 si la bande est plus grand ou égal à {@link #getColumnX} et/ou
215: * {@link #getColumnY}. Cette implémentation devrait convenir pour des données
216: * se trouvant aussi bien avant qu'après les colonnes <var>x</var>
217: * et <var>y</var>, même si ces dernières ne sont pas consécutives.
218: *
219: * @param imageIndex Index de l'image à lire.
220: * @param band Bande de l'image à lire.
221: * @return Numéro de colonne des données de l'image.
222: * @throws IOException si l'opération nécessitait une lecture du fichier (par exemple
223: * des informations inscrites dans un en-tête) et que cette lecture a échouée.
224: */
225: private int getColumn(final int imageIndex, int band)
226: throws IOException {
227: final int xColumn = getCheckedColumnX(imageIndex);
228: final int yColumn = getCheckedColumnY(imageIndex);
229: if (band >= Math.min(xColumn, yColumn))
230: band++;
231: if (band >= Math.max(xColumn, yColumn))
232: band++;
233: return band;
234: }
235:
236: /**
237: * Set the input source. It should be one of the following object, in preference order:
238: * {@link java.io.File}, {@link java.net.URL}, {@link java.io.BufferedReader}.
239: * {@link java.io.Reader}, {@link java.io.InputStream} or
240: * {@link javax.imageio.stream.ImageInputStream}.
241: */
242: //@Override
243: public void setInput(final Object input,
244: final boolean seekForwardOnly, final boolean ignoreMetadata) {
245: clear();
246: super .setInput(input, seekForwardOnly, ignoreMetadata);
247: }
248:
249: /**
250: * Returns the number of bands available for the specified image.
251: *
252: * @param imageIndex The image index.
253: * @throws IOException if an error occurs reading the information from the input source.
254: */
255: //@Override
256: public int getNumBands(final int imageIndex) throws IOException {
257: return getRecords(imageIndex).getColumnCount()
258: - (getCheckedColumnX(imageIndex) == getCheckedColumnY(imageIndex) ? 1
259: : 2);
260: }
261:
262: /**
263: * Returns the width in pixels of the given image within the input source.
264: *
265: * @param imageIndex the index of the image to be queried.
266: * @return Image width.
267: * @throws IOException If an error occurs reading the width information from the input source.
268: */
269: public int getWidth(final int imageIndex) throws IOException {
270: return getRecords(imageIndex).getPointCount(
271: getCheckedColumnX(imageIndex), getGridTolerance());
272: }
273:
274: /**
275: * Returns the height in pixels of the given image within the input source.
276: *
277: * @param imageIndex the index of the image to be queried.
278: * @return Image height.
279: * @throws IOException If an error occurs reading the height information from the input source.
280: */
281: public int getHeight(final int imageIndex) throws IOException {
282: return getRecords(imageIndex).getPointCount(
283: getCheckedColumnY(imageIndex), getGridTolerance());
284: }
285:
286: /**
287: * Returns metadata associated with the given image.
288: * Calling this method may force loading of full image.
289: *
290: * @param imageIndex The image index.
291: * @return The metadata, or {@code null} if none.
292: * @throws IOException If an error occurs reading the data information from the input source.
293: */
294: //@Override
295: public IIOMetadata getImageMetadata(final int imageIndex)
296: throws IOException {
297: checkImageIndex(imageIndex);
298: if (ignoreMetadata) {
299: return null;
300: }
301: final GeographicMetadata metadata = new GeographicMetadata(this );
302: final ImageGeometry geometry = metadata.getGeometry();
303: /*
304: * Computes the smallest bounding box containing the full image in user coordinates.
305: * This implementation searchs for minimum and maximum values in x and y columns as
306: * returned by getColumnX() and getColumnY(). Reminder: xmax and ymax are INCLUSIVE
307: * in the code below, as well as (width-1) and (height-1).
308: */
309: final float tolerance = getGridTolerance();
310: final RecordList records = getRecords(imageIndex);
311: final int xColumn = getCheckedColumnX(imageIndex);
312: final int yColumn = getCheckedColumnY(imageIndex);
313: final int width = records.getPointCount(xColumn, tolerance);
314: final int height = records.getPointCount(yColumn, tolerance);
315: final double xmin = records.getMinimum(xColumn);
316: final double ymin = records.getMinimum(yColumn);
317: final double xmax = records.getMaximum(xColumn);
318: final double ymax = records.getMaximum(yColumn);
319: geometry.addCoordinateRange(0, width - 1, xmin, xmax);
320: geometry.addCoordinateRange(0, height - 1, ymin, ymax);
321: geometry.setPixelOrientation("center");
322: /*
323: * Now adds the valid range of sample values for each band.
324: */
325: final int numBands = records.getColumnCount()
326: - (xColumn == yColumn ? 1 : 2);
327: for (int band = 0; band < numBands; band++) {
328: final int column = getColumn(imageIndex, band);
329: metadata.getBand(band).setValidRange(
330: records.getMinimum(column),
331: records.getMaximum(column));
332: }
333: return metadata;
334: }
335:
336: /**
337: * Rounds the specified values. This method is invoked automatically by the {@link #read read}
338: * method while reading an image. It provides a place where to fix rounding errors in latitude
339: * and longitude coordinates. For example if longitudes have a step 1/6° but are written with
340: * only 3 decimal digits, then we get {@linkplain #getColumnX x} values like {@code 10.000},
341: * {@code 10.167}, {@code 10.333}, <cite>etc.</cite>, which can leads to an error of 0.001°
342: * in longitude. This error may cause {@code TextRecordImageReader} to fails validation tests
343: * and throws an {@link javax.imageio.IIOException}: "<cite>Points dont seem to be distributed
344: * on a regular grid</cite>". A work around is to multiply the <var>x</var> and <var>y</var>
345: * coordinates by 6, round to the nearest integer and divide them by 6.
346: * <p>
347: * The default implementation do nothing.
348: *
349: * @param values The values to round in place.
350: */
351: protected void round(double[] values) {
352: }
353:
354: /**
355: * Retourne les données de l'image à l'index spécifié. Si cette image avait déjà été lue, ses
356: * données seront retournées immédiatement. Sinon, cette image sera lue ainsi que toutes les
357: * images qui précèdent {@code imageIndex} et qui n'avaient pas encore été lues. Que ces
358: * images précédentes soient mémorisées ou oubliées dépend de {@link #seekForwardOnly}.
359: *
360: * @param imageIndex Index de l'image à lire.
361: * @return Les données de l'image. Cette méthode ne retourne jamais {@code null}.
362: * @throws IOException si une erreur est survenue lors de la lecture du flot,
363: * ou si des nombres n'étaient pas correctement formatés dans le flot.
364: * @throws IndexOutOfBoundsException si l'index spécifié est en dehors des
365: * limites permises ou si aucune image n'a été conservée à cet index.
366: */
367: private RecordList getRecords(final int imageIndex)
368: throws IOException {
369: clearAbortRequest();
370: checkImageIndex(imageIndex);
371: if (imageIndex >= nextImageIndex) {
372: processImageStarted(imageIndex);
373: final BufferedReader reader = getReader();
374: final long origine = getStreamPosition(reader);
375: final long length = getStreamLength(nextImageIndex,
376: imageIndex + 1);
377: long nextProgressPosition = (origine >= 0 && length > 0) ? 0
378: : Long.MAX_VALUE;
379: for (; nextImageIndex <= imageIndex; nextImageIndex++) {
380: /*
381: * Réduit la consommation de mémoire des images précédentes. On ne réduit
382: * pas celle de l'image courante, puisque la plupart du temps le tableau
383: * sera bientôt détruit de toute façon.
384: */
385: if (seekForwardOnly) {
386: minIndex = nextImageIndex;
387: }
388: if (nextImageIndex != 0 && data != null) {
389: final RecordList records = data[nextImageIndex - 1];
390: if (records != null) {
391: if (seekForwardOnly) {
392: data[nextImageIndex - 1] = null;
393: } else {
394: records.trimToSize();
395: }
396: }
397: }
398: /*
399: * Procède à la lecture de chacune des lignes de données. Que ces lignes
400: * soient mémorisées ou pas dépend de l'image que l'on est en train de
401: * décoder ainsi que de la valeur de {@link #seekForwardOnly}.
402: */
403: double[] values = null;
404: RecordList records = null;
405: final boolean keep = (nextImageIndex == imageIndex)
406: || !seekForwardOnly;
407: final int xColumn = getCheckedColumnX(nextImageIndex);
408: final int yColumn = getCheckedColumnY(nextImageIndex);
409: final double padValue = getPadValue(nextImageIndex);
410: final LineFormat lineFormat = getLineFormat(nextImageIndex);
411: try {
412: String line;
413: while ((line = reader.readLine()) != null) {
414: if (isComment(line)
415: || lineFormat.setLine(line) == 0) {
416: continue;
417: }
418: values = lineFormat.getValues(values);
419: for (int i = 0; i < values.length; i++) {
420: if (i != xColumn && i != yColumn
421: && values[i] == padValue) {
422: values[i] = Double.NaN;
423: }
424: }
425: round(values);
426: if (keep) {
427: if (records == null) {
428: final int expectedLineCount = Math
429: .max(
430: 8,
431: Math
432: .min(
433: 65536,
434: Math
435: .round(length
436: / (expectedDatumLength * values.length))));
437: records = new RecordList(values.length,
438: expectedLineCount);
439: }
440: records.add(values);
441: }
442: final long position = getStreamPosition(reader)
443: - origine;
444: if (position >= nextProgressPosition) {
445: processImageProgress(position
446: * (100f / length));
447: nextProgressPosition = position
448: + PROGRESS_INTERVAL;
449: if (abortRequested()) {
450: processReadAborted();
451: return records;
452: }
453: }
454: }
455: } catch (ParseException exception) {
456: throw new IIOException(getPositionString(exception
457: .getLocalizedMessage()), exception);
458: }
459: /*
460: * Après la lecture d'une image, vérifie s'il y avait un nombre suffisant de lignes.
461: * Une exception sera lancée si l'image ne contenait pas au moins deux lignes. On
462: * ajustera ensuite le nombre moyens de caractères par données.
463: */
464: if (records != null) {
465: final int lineCount = records.getLineCount();
466: if (lineCount < 2) {
467: throw new IIOException(
468: getPositionString(Errors
469: .format(ErrorKeys.FILE_HAS_TOO_FEW_DATA)));
470: }
471: if (data == null) {
472: data = new RecordList[imageIndex + 1];
473: } else if (data.length <= imageIndex) {
474: data = (RecordList[]) XArray.resize(data,
475: imageIndex + 1);
476: }
477: data[nextImageIndex] = records;
478: final float meanDatumLength = (getStreamPosition(reader) - origine)
479: / (float) records.getDataCount();
480: if (meanDatumLength > 0)
481: expectedDatumLength = meanDatumLength;
482: }
483: }
484: processImageComplete();
485: }
486: /*
487: * Une fois les lectures terminées, retourne les données de l'image
488: * demandée. Une exception sera lancée si ces données n'ont pas été
489: * conservées.
490: */
491: if (data != null && imageIndex < data.length) {
492: final RecordList records = data[imageIndex];
493: if (records != null) {
494: return records;
495: }
496: }
497: throw new IndexOutOfBoundsException(String.valueOf(imageIndex));
498: }
499:
500: /**
501: * Reads the image indexed by {@code imageIndex} and returns it as a complete buffered image.
502: *
503: * @param imageIndex the index of the image to be retrieved.
504: * @param param Parameters used to control the reading process, or {@code null}.
505: * @return the desired portion of the image.
506: * @throws IOException if an error occurs during reading.
507: */
508: public BufferedImage read(final int imageIndex,
509: final ImageReadParam param) throws IOException {
510: final float tolerance = getGridTolerance();
511: final int xColumn = getCheckedColumnX(imageIndex);
512: final int yColumn = getCheckedColumnY(imageIndex);
513: final RecordList records = getRecords(imageIndex);
514: final int width = records.getPointCount(xColumn, tolerance);
515: final int height = records.getPointCount(yColumn, tolerance);
516: final int numSrcBands = records.getColumnCount()
517: - (xColumn == yColumn ? 1 : 2);
518: /*
519: * Extracts user's parameters
520: */
521: final int[] srcBands;
522: final int[] dstBands;
523: final int sourceXSubsampling;
524: final int sourceYSubsampling;
525: final int subsamplingXOffset;
526: final int subsamplingYOffset;
527: final int destinationXOffset;
528: final int destinationYOffset;
529: if (param != null) {
530: srcBands = param.getSourceBands();
531: dstBands = param.getDestinationBands();
532: final Point offset = param.getDestinationOffset();
533: sourceXSubsampling = param.getSourceXSubsampling();
534: sourceYSubsampling = param.getSourceYSubsampling();
535: subsamplingXOffset = param.getSubsamplingXOffset();
536: subsamplingYOffset = param.getSubsamplingYOffset();
537: destinationXOffset = offset.x;
538: destinationYOffset = offset.y;
539: } else {
540: srcBands = null;
541: dstBands = null;
542: sourceXSubsampling = 1;
543: sourceYSubsampling = 1;
544: subsamplingXOffset = 0;
545: subsamplingYOffset = 0;
546: destinationXOffset = 0;
547: destinationYOffset = 0;
548: }
549: /*
550: * Initializes...
551: */
552: final int numDstBands = (dstBands != null) ? dstBands.length
553: : (srcBands != null) ? srcBands.length : numSrcBands;
554: final BufferedImage image = getDestination(imageIndex, param,
555: width, height, null); // TODO
556: checkReadParamBandSettings(param, numSrcBands, image
557: .getSampleModel().getNumBands());
558:
559: final Rectangle srcRegion = new Rectangle();
560: final Rectangle dstRegion = new Rectangle();
561: computeRegions(param, width, height, image, srcRegion,
562: dstRegion);
563: final int sourceXMin = srcRegion.x;
564: final int sourceYMin = srcRegion.y;
565: final int sourceXMax = srcRegion.width + sourceXMin;
566: final int sourceYMax = srcRegion.height + sourceYMin;
567:
568: final WritableRaster raster = image.getRaster();
569: final int rasterWidth = raster.getWidth();
570: final int rasterHeigth = raster.getHeight();
571: final int columnCount = records.getColumnCount();
572: final int dataCount = records.getDataCount();
573: final float[] data = records.getData();
574: final double xmin = records.getMinimum(xColumn);
575: final double ymin = records.getMinimum(yColumn);
576: final double xmax = records.getMaximum(xColumn);
577: final double ymax = records.getMaximum(yColumn);
578: final double scaleX = (width - 1) / (xmax - xmin);
579: final double scaleY = (height - 1) / (ymax - ymin);
580: /*
581: * Clears the image area. All values are set to NaN.
582: */
583: if (CLEAR) {
584: final int minX = dstRegion.x;
585: final int minY = dstRegion.y;
586: final int maxX = dstRegion.width + minX;
587: final int maxY = dstRegion.height + minY;
588: for (int b = (dstBands != null) ? dstBands.length
589: : numDstBands; --b >= 0;) {
590: final int band = (dstBands != null) ? dstBands[b] : b;
591: for (int y = minY; y < maxY; y++) {
592: for (int x = minX; x < maxX; x++) {
593: raster.setSample(x, y, band, Float.NaN);
594: }
595: }
596: }
597: }
598: /*
599: * Computes column numbers corresponding to source bands,
600: * and start storing values into the image.
601: */
602: final int[] columns = new int[(srcBands != null) ? srcBands.length
603: : numDstBands];
604: for (int i = 0; i < columns.length; i++) {
605: columns[i] = getColumn(imageIndex,
606: srcBands != null ? srcBands[i] : i);
607: }
608: for (int i = 0; i < dataCount; i += columnCount) {
609: /*
610: * On convertit maintenant la coordonnée (x,y) logique en coordonnée pixel. Cette
611: * coordonnée pixel se réfère à l'image "source"; elle ne se réfère pas encore à
612: * l'image destination. Elle doit obligatoirement être entière. Plus loin, nous
613: * tiendrons compte du "subsampling".
614: */
615: final double fx = (data[i + xColumn] - xmin) * scaleX; // (fx,fy) may be NaN: Use
616: final double fy = (ymax - data[i + yColumn]) * scaleY; // "!(abs(...)<=tolerance)".
617: int x = (int) Math.round(fx); // This conversion is not the same than
618: int y = (int) Math.round(fy); // getTransform(), but it should be ok.
619: if (!(Math.abs(x - fx) <= tolerance)) {
620: fireBadCoordinate(data[i + xColumn]);
621: continue;
622: }
623: if (!(Math.abs(y - fy) <= tolerance)) {
624: fireBadCoordinate(data[i + yColumn]);
625: continue;
626: }
627: if (x >= sourceXMin && x < sourceXMax && y >= sourceYMin
628: && y < sourceYMax) {
629: x -= subsamplingXOffset;
630: y -= subsamplingYOffset;
631: if ((x % sourceXSubsampling) == 0
632: && (y % sourceYSubsampling) == 0) {
633: x = x / sourceXSubsampling
634: + (destinationXOffset - sourceXMin);
635: y = y / sourceYSubsampling
636: + (destinationYOffset - sourceYMin);
637: if (x < rasterWidth && y < rasterHeigth) {
638: for (int j = 0; j < columns.length; j++) {
639: raster
640: .setSample(
641: x,
642: y,
643: (dstBands != null ? dstBands[j]
644: : j), data[i
645: + columns[j]]);
646: }
647: }
648: }
649: }
650: }
651: return image;
652: }
653:
654: /**
655: * Prévient qu'une coordonnée est mauvaise. Cette méthode est appelée lors de la lecture
656: * s'il a été détecté qu'une coordonnée est en dehors des limites prévues, ou qu'elle ne
657: * correspond pas à des coordonnées pixels entières.
658: */
659: private void fireBadCoordinate(final float coordinate) {
660: processWarningOccurred(getPositionString(Errors.format(
661: ErrorKeys.BAD_COORDINATE_$1, new Float(coordinate))));
662: }
663:
664: /**
665: * Supprime les données de toutes les images
666: * qui avait été conservées en mémoire.
667: */
668: private void clear() {
669: data = null;
670: nextImageIndex = 0;
671: expectedDatumLength = 10.4f;
672: }
673:
674: /**
675: * Restores the {@code TextRecordImageReader} to its initial state.
676: */
677: //@Override
678: public void reset() {
679: clear();
680: super .reset();
681: }
682:
683: /**
684: * Service provider interface (SPI) for {@link TextRecordImageReader}s. This SPI provides
685: * necessary implementation for creating default {@link TextRecordImageReader} using default
686: * locale and character set. Subclasses can set some fields at construction time in order to
687: * tune the reader to a particular environment, e.g.:
688: *
689: * <blockquote><pre>
690: * public final class CLSImageReaderSpi extends TextRecordImageReader.Spi {
691: * public CLSImageReaderSpi() {
692: * {@link #names names} = new String[] {"CLS"};
693: * {@link #MIMETypes MIMETypes} = new String[] {"text/x-records-CLS"};
694: * {@link #vendorName vendorName} = "Institut de Recherche pour le Développement";
695: * {@link #version version} = "1.0";
696: * {@link #locale locale} = Locale.US;
697: * {@link #charset charset} = Charset.forName("ISO-LATIN-1");
698: * {@link #padValue padValue} = 9999;
699: * }
700: * }
701: * </pre></blockquote>
702: *
703: * (Note: fields {@code vendorName} and {@code version} are only informatives).
704: * There is no need to override any method in this example. However, developers
705: * can gain more control by creating subclasses of {@link TextRecordImageReader}
706: * and {@code Spi}.
707: *
708: * @since 2.1
709: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextRecordImageReader.java $
710: * @version $Id: TextRecordImageReader.java 27629 2007-10-26 09:59:20Z desruisseaux $
711: * @author Martin Desruisseaux
712: */
713: public static class Spi extends TextImageReader.Spi {
714: /**
715: * The format names for the default {@link TextRecordImageReader} configuration.
716: */
717: private static final String[] NAMES = { "records" };
718:
719: /**
720: * The mime types for the default {@link TextRecordImageReader} configuration.
721: */
722: private static final String[] MIME_TYPES = { "text/x-records" };
723:
724: /**
725: * 0-based column number for <var>x</var> values. The default value is 0.
726: *
727: * @see TextRecordImageReader#getColumnX
728: * @see TextRecordImageReader#parseLine
729: */
730: protected int xColumn;
731:
732: /**
733: * 0-based column number for <var>y</var> values. The default value is 1.
734: *
735: * @see TextRecordImageReader#getColumnY
736: * @see TextRecordImageReader#parseLine
737: */
738: protected int yColumn;
739:
740: /**
741: * A tolerance factor during decoding, between 0 and 1. During decoding,
742: * the image reader compute cell's width and height (i.e. the smallest
743: * non-null difference between ordinates in a given column: <var>x</var>
744: * for cell's width and <var>y</var> for cell's height). Then, it checks
745: * if every coordinate points fall on a grid having this cell's size. If
746: * a point depart from more than {@code gridTolerance} percent of cell's
747: * width or height, an exception is thrown.
748: * <p>
749: * {@code gridTolerance} should be a small number like {@code 1E-5f}
750: * or {@code 1E-3f}. The later is more tolerant than the former.
751: */
752: protected float gridTolerance = EPS;
753:
754: /**
755: * Constructs a default {@code TextRecordImageReader.Spi}. This constructor
756: * provides the following defaults in addition to the defaults defined in the
757: * {@linkplain TextImageReader.Spi#Spi super-class constructor}:
758: *
759: * <ul>
760: * <li>{@link #names} = {@code "records"}</li>
761: * <li>{@link #MIMETypes} = {@code "text/x-records"}</li>
762: * <li>{@link #pluginClassName} = {@code "org.geotools.image.io.text.TextRecordImageReader"}</li>
763: * <li>{@link #vendorName} = {@code "Geotools"}</li>
764: * <li>{@link #xColumn} = {@code 0}</li>
765: * <li>{@link #yColumn} = {@code 1}</li>
766: * </ul>
767: *
768: * For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
769: * can assign new arrays, but should not modify the default array content.
770: */
771: public Spi() {
772: names = NAMES;
773: MIMETypes = MIME_TYPES;
774: pluginClassName = "org.geotools.image.io.text.TextRecordImageReader";
775: vendorName = "Geotools";
776: version = "2.4";
777: xColumn = 0;
778: yColumn = 1;
779: gridTolerance = EPS;
780: }
781:
782: /**
783: * Returns a brief, human-readable description of this service provider
784: * and its associated implementation. The resulting string should be
785: * localized for the supplied locale, if possible.
786: *
787: * @param locale A Locale for which the return value should be localized.
788: * @return A String containing a description of this service provider.
789: */
790: public String getDescription(final Locale locale) {
791: return Descriptions.getResources(locale).getString(
792: DescriptionKeys.CODEC_GRID);
793: }
794:
795: /**
796: * Returns an instance of the ImageReader implementation associated
797: * with this service provider.
798: *
799: * @param extension An optional extension object, which may be null.
800: * @return An image reader instance.
801: * @throws IOException if the attempt to instantiate the reader fails.
802: */
803: public ImageReader createReaderInstance(final Object extension)
804: throws IOException {
805: return new TextRecordImageReader(this );
806: }
807:
808: /**
809: * Returns {@code true} if the specified row length is valid. The default implementation
810: * returns {@code true} if the row seems "short", where "short" is arbitrary fixed to 10
811: * columns. This is an arbitrary choice, which is why this method is not public. It may
812: * be changed in any future Geotools version.
813: */
814: //@Override
815: boolean isValidColumnCount(final int count) {
816: return count >= (xColumn == yColumn ? 2 : 3) && count <= 10;
817: }
818: }
819: }
|