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.io.*; // Many imports, including some for javadoc only.
020: import java.nio.charset.Charset;
021: import java.net.URL;
022: import java.net.URLConnection;
023: import java.util.Locale;
024: import java.text.NumberFormat;
025: import java.text.DecimalFormat;
026: import java.text.FieldPosition;
027: import java.awt.image.DataBuffer;
028: import javax.imageio.IIOImage;
029: import javax.imageio.ImageWriteParam;
030: import javax.imageio.spi.ImageWriterSpi;
031: import javax.imageio.stream.ImageOutputStream;
032: import javax.media.jai.iterator.RectIter;
033:
034: import org.geotools.resources.XMath;
035: import org.geotools.image.io.StreamImageWriter;
036:
037: /**
038: * Base class for text image encoders. "Text images" are usually ASCII files
039: * containing pixel values (often geophysical values, like sea level anomalies).
040: *
041: * @since 2.4
042: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextImageWriter.java $
043: * @version $Id: TextImageWriter.java 26833 2007-09-04 18:01:08Z desruisseaux $
044: * @author Martin Desruisseaux
045: */
046: public abstract class TextImageWriter extends StreamImageWriter {
047: /**
048: * Maximum number of digits to be allowed by {@link #createNumberFormat}.
049: */
050: private static final int MAXIMUM_DIGITS = 12;
051:
052: /**
053: * Do not force the formatting of fraction digits if the sample values are equal or
054: * greater than 1E+6.
055: */
056: private static final double NODIGITS_THRESHOLD = 1E+6;
057:
058: /**
059: * Number of digits to check after the last one, as 10<sup>-n</sup>. The default value is
060: * 1E-1. This means that if the digit immediately after the last one is 0, we will consider
061: * that we have reached the intented precision.
062: *
063: * For adjusting the number of digits to check after the last one, just put this number
064: * as a negative power in place of the "-1" above.
065: */
066: private static final double DELTA_THRESHOLD = 1E-1;
067:
068: /**
069: * The maximum value found during the last call to {@link #createNumberFormat}.
070: */
071: private double maximum;
072:
073: /**
074: * {@link #output} as a writer, or {@code null} if none.
075: *
076: * @see #getWriter
077: */
078: private BufferedWriter writer;
079:
080: /**
081: * Constructs a {@code TextImageWriter}.
082: *
083: * @param originatingProvider The {@code ImageWriterSpi} that
084: * is constructing this object, or {@code null}.
085: */
086: protected TextImageWriter(final ImageWriterSpi provider) {
087: super (provider);
088: }
089:
090: /**
091: * Returns the locale to use for encoding values, or {@code null} for the
092: * {@linkplain Locale#getDefault default}. The default implementation returns the
093: * {@linkplain Spi#locale locale} specified to the {@link Spi} object given to this
094: * {@code TextImageWriter} constructor. Subclasses can override this method if they
095: * want to specify the data locale in some other way.
096: * <p>
097: * <b>Note:</b> This locale should not be confused with {@link #getLocale}.
098: *
099: * @param parameters The write parameters, or {@code null} for the defaults.
100: * @return The locale to use for parsing numbers in the image file.
101: *
102: * @see Spi#locale
103: */
104: protected Locale getDataLocale(final ImageWriteParam parameters) {
105: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).locale
106: : null;
107: }
108:
109: /**
110: * Returns the character set to use for encoding the string to the output stream.
111: * The default implementation returns the {@linkplain Spi#charset character set}
112: * specified to the {@link Spi} object given to this {@code TextImageWriter} constructor.
113: * Subclasses can override this method if they want to specify the character encoding in
114: * some other way.
115: *
116: * @param parameters The write parameters, or {@code null} for the defaults.
117: * @return The character encoding, or {@code null} for the platform default encoding.
118: * @throws IOException If reading from the output stream failed.
119: *
120: * @see Spi#charset
121: */
122: protected Charset getCharset(final ImageWriteParam parameters)
123: throws IOException {
124: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).charset
125: : null;
126: }
127:
128: /**
129: * Returns the line separator to use when writing to the output stream. The default
130: * implementation returns the {@linkplain Spi#lineSeparator line separator} specified
131: * to the {@link Spi} object given to this {@code TextImageWriter} constructor. Subclasses
132: * can override this method if they want to specify the line separator in some other way.
133: *
134: * @param parameters The write parameters, or {@code null} for the defaults.
135: * @return The line separator to use for writting the image.
136: *
137: * @see Spi#lineSeparator
138: */
139: protected String getLineSeparator(final ImageWriteParam parameters) {
140: if (originatingProvider instanceof Spi) {
141: final String lineSeparator = ((Spi) originatingProvider).lineSeparator;
142: if (lineSeparator != null) {
143: return lineSeparator;
144: }
145: }
146: return System.getProperty("line.separator", "\n");
147: }
148:
149: /**
150: * Returns the {@linkplain #output output} as an {@linkplain BufferedWriter buffered writer}.
151: * If the output is already a buffered writer, it is returned unchanged. Otherwise this method
152: * creates a new {@linkplain BufferedWriter buffered writer} from various output types
153: * including {@link File}, {@link URL}, {@link URLConnection}, {@link Writer},
154: * {@link OutputStream} and {@link ImageOutputStream}.
155: * <p>
156: * This method creates a new {@linkplain BufferedWriter writer} only when first invoked.
157: * All subsequent calls will returns the same instance. Consequently, the returned writer
158: * should never be closed by the caller. It may be {@linkplain #close closed} automatically
159: * when {@link #setOutput setOutput(Object)}, {@link #reset() reset()} or {@link #dispose()
160: * dispose()} methods are invoked.
161: *
162: * @param parameters The write parameters, or {@code null} for the defaults.
163: * @return {@link #getOutput} as a {@link BufferedWriter}.
164: * @throws IllegalStateException if the {@linkplain #output output} is not set.
165: * @throws IOException If the output stream can't be created for an other reason.
166: *
167: * @see #getOutput
168: * @see #getOutputStream
169: */
170: protected BufferedWriter getWriter(final ImageWriteParam parameters)
171: throws IllegalStateException, IOException {
172: if (writer == null) {
173: final Object output = getOutput();
174: if (output instanceof BufferedWriter) {
175: writer = (BufferedWriter) output;
176: closeOnReset = null; // We don't own the underlying writer, so don't close it.
177: } else if (output instanceof Writer) {
178: writer = new BufferedWriter((Writer) output);
179: closeOnReset = null; // We don't own the underlying writer, so don't close it.
180: } else {
181: final OutputStream stream = getOutputStream();
182: final Charset charset = getCharset(parameters);
183: writer = new BufferedWriter(
184: (charset != null) ? new OutputStreamWriter(
185: stream, charset)
186: : new OutputStreamWriter(stream));
187: if (closeOnReset == stream) {
188: closeOnReset = writer;
189: }
190: }
191: }
192: return writer;
193: }
194:
195: /**
196: * Returns a number format to be used for formatting the sample values in the given image.
197: *
198: * @param image The image or raster to be written.
199: * @param parameters The write parameters, or {@code null} if the whole image will be written.
200: * @return A number format appropriate for the given image.
201: */
202: protected strictfp NumberFormat createNumberFormat(
203: final IIOImage image, final ImageWriteParam parameters) {
204: final Locale locale = getDataLocale(parameters);
205: final int type = image.hasRaster() ? image.getRaster()
206: .getTransferType() : image.getRenderedImage()
207: .getSampleModel().getDataType();
208: if (type != DataBuffer.TYPE_FLOAT
209: && type != DataBuffer.TYPE_DOUBLE) {
210: maximum = Short.MAX_VALUE; // TODO: This is not really accurate...
211: return (locale != null) ? NumberFormat
212: .getIntegerInstance(locale) : NumberFormat
213: .getIntegerInstance();
214: }
215: int digits = 0;
216: double multiple = 1;
217: maximum = Double.NEGATIVE_INFINITY;
218: final RectIter iterator = createRectIter(image, parameters);
219: if (!iterator.finishedBands())
220: do {
221: if (!iterator.finishedLines())
222: do {
223: if (!iterator.finishedPixels())
224: do {
225: final double value = Math.abs(iterator
226: .getSampleDouble());
227: if (Double.isInfinite(value)) {
228: continue;
229: }
230: // Following code is NaN tolerant - no need for explicit check.
231: if (value > maximum) {
232: maximum = value;
233: }
234: while (true) {
235: double scaled = value * multiple;
236: if (type == DataBuffer.TYPE_FLOAT) {
237: scaled = (float) scaled; // Drops the extra digits.
238: }
239: // Condition below uses '!' in order to cath NaN values.
240: if (!(StrictMath.abs(scaled
241: - StrictMath.rint(scaled)) >= DELTA_THRESHOLD)) {
242: break;
243: }
244: if (++digits > MAXIMUM_DIGITS) {
245: return NumberFormat
246: .getNumberInstance(locale);
247: }
248: multiple *= 10;
249: }
250: } while (!iterator.nextPixelDone());
251: iterator.startPixels();
252: } while (!iterator.nextLineDone());
253: iterator.startLines();
254: } while (!iterator.nextBandDone());
255: /*
256: * 'digits' should now be the exact number of fraction digits to format. However the above
257: * algorithm do not work if all values are smaller (in absolute value) to DELTA_THRESHOLD,
258: * in which case 'digits' is still set to 0. In such case it is better to keep the default
259: * format unchanged, since it should be generic enough.
260: */
261: final NumberFormat format = (locale != null) ? NumberFormat
262: .getNumberInstance(locale) : NumberFormat
263: .getNumberInstance();
264: if (digits != 0 || maximum >= DELTA_THRESHOLD) {
265: format.setMaximumFractionDigits(digits);
266: if (maximum < NODIGITS_THRESHOLD) {
267: format.setMinimumFractionDigits(digits);
268: }
269: }
270: return format;
271: }
272:
273: /**
274: * Returns the expected position of the fraction part for numbers to be formatted using the
275: * given format. This method should be invoked after {@link #createNumberFormat}, but the
276: * given format doesn't need to be the instance returned by the later.
277: *
278: * @param format The format to be used for formatting numbers.
279: * @return The expected position of the fraction part.
280: *
281: * @todo I don't really kwon what to do if the format is not an instance of
282: * {@link DecimalFormat}...
283: */
284: protected FieldPosition getExpectedFractionPosition(
285: final NumberFormat format) {
286: int width = Math.max(
287: (int) Math.floor(XMath.log10(maximum)) + 1, format
288: .getMinimumIntegerDigits());
289: int digits = Math.min(format.getMaximumFractionDigits(),
290: MAXIMUM_DIGITS);
291: if (format instanceof DecimalFormat) {
292: final DecimalFormat decimal = (DecimalFormat) format;
293: if (digits > 0 || decimal.isDecimalSeparatorAlwaysShown()) {
294: width++;
295: }
296: width += Math.max(decimal.getNegativePrefix().length(),
297: decimal.getPositivePrefix().length());
298: digits += Math.max(decimal.getNegativeSuffix().length(),
299: decimal.getPositiveSuffix().length());
300: }
301: final FieldPosition position = new FieldPosition(
302: NumberFormat.FRACTION_FIELD);
303: position.setBeginIndex(width);
304: position.setEndIndex(width += digits);
305: // 'width' is now the full width. We don't do anything with it at this time,
306: // but maybe in some future version...
307: return position;
308: }
309:
310: /**
311: * Closes the writer created by {@link #getWriter()}. This method does nothing if
312: * the writer is the {@linkplain #output output} instance given by the user rather
313: * than a writer created by this class from a {@link File} or {@link URL} output.
314: *
315: * @see #closeOnReset
316: */
317: //@Override
318: protected void close() throws IOException {
319: writer = null;
320: super .close();
321: }
322:
323: /**
324: * Service provider interface (SPI) for {@link TextImageWriter}s. This SPI provides a
325: * convenient way to control the {@link TextImageWriter} character encoding: the
326: * {@link #charset} field. For example, many {@code Spi} subclasses will put the
327: * following line in their constructor:
328: *
329: * <blockquote><pre>
330: * {@link #charset} = Charset.forName("ISO-LATIN-1"); // ISO Latin Alphabet No. 1 (ISO-8859-1)
331: * </pre></blockquote>
332: *
333: * @since 2.4
334: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextImageWriter.java $
335: * @version $Id: TextImageWriter.java 26833 2007-09-04 18:01:08Z desruisseaux $
336: * @author Martin Desruisseaux
337: */
338: public static abstract class Spi extends StreamImageWriter.Spi {
339: /**
340: * List of legal output types for {@link TextImageWriter}.
341: */
342: private static final Class[] INPUT_TYPES = new Class[] {
343: File.class, URL.class, URLConnection.class,
344: Writer.class, OutputStream.class,
345: ImageOutputStream.class, String.class // To be interpreted as file path.
346: };
347:
348: /**
349: * Default list of file extensions.
350: */
351: private static final String[] EXTENSIONS = new String[] {
352: "txt", "TXT", "asc", "ASC", "dat", "DAT" };
353:
354: /**
355: * Character encoding, or {@code null} for the default. This field is initially
356: * {@code null}. A value shall be set by subclasses if the files to be encoded
357: * use some specific character encoding.
358: *
359: * @see TextImageWriter#getCharset
360: */
361: protected Charset charset;
362:
363: /**
364: * The locale for numbers formatting. For example {@link Locale#US} means that
365: * numbers are expected to use dot as decimal separator. This field is initially
366: * {@code null}, which means that default locale should be used.
367: *
368: * @see TextImageWriter#getDataLocale
369: */
370: protected Locale locale;
371:
372: /**
373: * The line separator to use, or {@code null} for the system default.
374: *
375: * @see TextImageWriter#getLineSeparator
376: */
377: protected String lineSeparator;
378:
379: /**
380: * Constructs a quasi-blank {@code TextImageWriter.Spi}. It is up to the subclass to
381: * initialize instance variables in order to provide working versions of all methods.
382: * This constructor provides the following defaults:
383: *
384: * <ul>
385: * <li>{@link #outputTypes} = {{@link File}, {@link URL}, {@link URLConnection},
386: * {@link Writer}, {@link OutputStream}, {@link ImageOutputStream}, {@link String}}</li>
387: *
388: * <li>{@link #suffixes} = {{@code "txt"}, {@code "asc"}, {@code "dat"}}
389: * (lowercases and uppercases)</li>
390: * </ul>
391: *
392: * For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
393: * can assign new arrays, but should not modify the default array content.
394: */
395: public Spi() {
396: outputTypes = INPUT_TYPES;
397: suffixes = EXTENSIONS;
398: }
399: }
400: }
|