001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.gce.image;
018:
019: import java.awt.RenderingHints;
020: import java.awt.geom.AffineTransform;
021: import java.awt.image.ComponentColorModel;
022: import java.awt.image.DataBuffer;
023: import java.awt.image.DirectColorModel;
024: import java.awt.image.IndexColorModel;
025: import java.awt.image.RenderedImage;
026: import java.io.BufferedWriter;
027: import java.io.File;
028: import java.io.FileOutputStream;
029: import java.io.FileWriter;
030: import java.io.IOException;
031: import java.io.PrintWriter;
032: import java.net.URL;
033: import java.util.Iterator;
034: import java.util.Set;
035:
036: import javax.imageio.ImageIO;
037: import javax.imageio.stream.ImageOutputStream;
038: import javax.media.jai.JAI;
039: import javax.media.jai.ParameterBlockJAI;
040:
041: import org.geotools.coverage.grid.GridCoverage2D;
042: import org.geotools.coverage.grid.io.AbstractGridCoverageWriter;
043: import org.geotools.data.DataSourceException;
044: import org.geotools.factory.Hints;
045: import org.geotools.image.ImageWorker;
046: import org.geotools.parameter.Parameter;
047: import org.geotools.referencing.operation.matrix.XAffineTransform;
048: import org.geotools.resources.CRSUtilities;
049: import org.opengis.coverage.grid.Format;
050: import org.opengis.coverage.grid.GridCoverage;
051: import org.opengis.coverage.grid.GridCoverageWriter;
052: import org.opengis.parameter.GeneralParameterValue;
053: import org.opengis.referencing.crs.CoordinateReferenceSystem;
054: import org.opengis.referencing.cs.CoordinateSystem;
055: import org.opengis.referencing.operation.TransformException;
056:
057: /**
058: * Writes a GridCoverage to a raster image file and an accompanying world file.
059: * The destination specified must point to the location of the raster file to
060: * write to, as this is how the format is determined. The directory that file is
061: * located in must also already exist.
062: *
063: * @author Simone Giannecchini
064: * @author rgould
065: * @author alessio fabiani
066: * @source $URL:
067: * http://svn.geotools.org/geotools/trunk/gt/plugin/image/src/org/geotools/gce/image/WorldImageWriter.java $
068: */
069: public final class WorldImageWriter extends AbstractGridCoverageWriter
070: implements GridCoverageWriter {
071: /** format for this writer */
072: private Format format = new WorldImageFormat();
073:
074: /**
075: * Format chosen for this writer.
076: *
077: * The default format is png.
078: */
079: private String extension = "png";
080:
081: /**
082: * Destination must be a File. The directory it resides in must already
083: * exist. It must point to where the raster image is to be located. The
084: * world image will be derived from there.
085: *
086: * @param destination
087: */
088: public WorldImageWriter(Object destination) {
089: this (destination, null);
090: }
091:
092: /**
093: * Destination must be a File. The directory it resides in must already
094: * exist. It must point to where the raster image is to be located. The
095: * world image will be derived from there.
096: *
097: * @param destination
098: */
099: public WorldImageWriter(Object destination, Hints hints) {
100: this .destination = destination;
101:
102: // convert everything into a file when possible
103: // we have to separate the handling of a file from the handling of an
104: // output stream due to the fact that the latter requires no world file.
105: if (this .destination instanceof String) {
106: destination = new File((String) destination);
107: } else if (this .destination instanceof URL) {
108: final URL url = ((URL) destination);
109: if (url.getProtocol().equalsIgnoreCase("file")) {
110: String auth = url.getAuthority();
111: String path = url.getPath();
112: if (auth != null && !auth.equals("")) {
113: path = "//" + auth + path;
114: }
115:
116: destination = new File(path);
117: } else {
118: throw new RuntimeException(
119: "WorldImageWriter::write:It is not possible writing to an URL!");
120: }
121: } /*
122: * else if (!(destination instanceof ImageOutputStream) &&
123: * !(destination instanceof File)) throw new RuntimeException(
124: * "WorldImageWriter::write:It is not possible writing to an URL!");
125: */
126:
127: // //
128: //
129: // managing hints
130: //
131: // //
132: if (hints != null) {
133: if (this .hints == null) {
134: this .hints = new Hints(Hints.LENIENT_DATUM_SHIFT,
135: Boolean.TRUE);
136: this .hints.add(new RenderingHints(JAI.KEY_TILE_CACHE,
137: null));
138: }
139: this .hints.add(hints);
140: }
141: }
142:
143: /**
144: * (non-Javadoc)
145: *
146: * @see org.opengis.coverage.grid.GridCoverageWriter#getFormat()
147: */
148: public Format getFormat() {
149: return format;
150: }
151:
152: /**
153: * Takes a GridCoverage and writes the image to the destination file. It
154: * then reads the format of the file and writes an accompanying world file.
155: * It will throw a FileFormatNotCompatibleWithGridCoverageException if
156: * Destination is not a File (URL is a read-only format!).
157: *
158: * @param coverage
159: * the GridCoverage to write.
160: * @param parameters
161: * no parameters are accepted. Currently ignored.
162: *
163: * @throws IllegalArgumentException
164: * DOCUMENT ME!
165: * @throws IOException
166: * DOCUMENT ME!
167: * @see org.opengis.coverage.grid.GridCoverageWriter#write(org.geotools.gc.GridCoverage,
168: * org.opengis.parameter.GeneralParameterValue[])
169: */
170: public void write(GridCoverage coverage,
171: GeneralParameterValue[] parameters)
172: throws IllegalArgumentException, IOException {
173: final GridCoverage2D gc = (GridCoverage2D) coverage;
174: // checking parameters
175: // if provided we have to use them
176: // specifically this is one of the way we can provide an output format
177: if (parameters != null) {
178: this .extension = ((Parameter) parameters[0]).stringValue();
179: }
180:
181: // /////////////////////////////////////////////////////////////////////
182: //
183: // WorldFile and projection file.
184: //
185: // ////////////////////////////////////////////////////////////////////
186: if (destination instanceof File) {
187: // files destinations
188: File imageFile = (File) destination;
189: final String path = imageFile.getAbsolutePath();
190: final int index = path.lastIndexOf(".");
191: final String baseFile = index >= 0 ? path.substring(0,
192: index) : path;
193:
194: // envelope and image
195: final RenderedImage image = gc.getRenderedImage();
196:
197: // world file
198: try {
199: createWorldFile(coverage, image, baseFile);
200: } catch (TransformException e) {
201: final IOException ex = new IOException();
202: ex.initCause(e);
203: throw ex;
204: }
205:
206: // projection file
207: createProjectionFile(baseFile, coverage
208: .getCoordinateReferenceSystem());
209:
210: }
211:
212: // /////////////////////////////////////////////////////////////////////
213: //
214: // Encoding of the original coverage
215: //
216: // ////////////////////////////////////////////////////////////////////
217: outStream = (destination instanceof ImageOutputStream) ? (ImageOutputStream) destination
218: : ImageIO.createImageOutputStream(destination);
219: if (outStream == null)
220: throw new IOException(
221: "WorldImageWriter::write:No image output stream avalaible for the provided destination");
222: this .encode(gc, outStream);
223:
224: }
225:
226: /**
227: * This method is responsible for creating a projection file using the WKT
228: * representation of this coverage's coordinate reference system. We can
229: * reuse this file in order to rebuild later the crs.
230: *
231: *
232: * @param baseFile
233: * @param coordinateReferenceSystem
234: * @throws IOException
235: */
236: private void createProjectionFile(final String baseFile,
237: final CoordinateReferenceSystem coordinateReferenceSystem)
238: throws IOException {
239: final File prjFile = new File(new StringBuffer(baseFile)
240: .append(".prj").toString());
241: BufferedWriter out = new BufferedWriter(new FileWriter(prjFile));
242: out.write(coordinateReferenceSystem.toWKT());
243: out.close();
244:
245: }
246:
247: /**
248: * This method is responsible fro creating a world file to georeference an
249: * image given the image bounding box and the image geometry. The name of
250: * the file is composed by the name of the image file with a proper
251: * extension, depending on the format (see WorldImageFormat). The projection
252: * is in the world file.
253: *
254: * @param gc
255: * Envelope of this image.
256: * @param image
257: * Image to be used.
258: * @param baseFile
259: * Basename and path for this image.
260: * @throws IOException
261: * In case we cannot create the world file.
262: * @throws TransformException
263: * @throws TransformException
264: */
265: private void createWorldFile(GridCoverage gc,
266: final RenderedImage image, final String baseFile)
267: throws IOException, TransformException {
268: // /////////////////////////////////////////////////////////////////////
269: //
270: // CRS information
271: //
272: // ////////////////////////////////////////////////////////////////////
273: final CoordinateReferenceSystem crs = CRSUtilities.getCRS2D(gc
274: .getCoordinateReferenceSystem());
275: final CoordinateSystem cs = crs.getCoordinateSystem();
276: final AffineTransform gridToWorld = (AffineTransform) gc
277: .getGridGeometry().getGridToCoordinateSystem();
278: final boolean lonFirst = (XAffineTransform
279: .getSwapXY(gridToWorld) != -1);
280:
281: // /////////////////////////////////////////////////////////////////////
282: //
283: // World File values
284: // It is worthwhile to note that we have to keep into account the fact
285: // that the axis could be swapped (LAT,lon) therefore when getting
286: // xPixSize and yPixSize we need to look for it a the right place
287: // inside the grid to world transform.
288: //
289: // ////////////////////////////////////////////////////////////////////
290: final double xPixelSize = (lonFirst) ? gridToWorld.getScaleX()
291: : gridToWorld.getShearY();
292: final double rotation1 = (lonFirst) ? gridToWorld.getShearX()
293: : gridToWorld.getScaleX();
294: final double rotation2 = (lonFirst) ? gridToWorld.getShearY()
295: : gridToWorld.getScaleY();
296: final double yPixelSize = (lonFirst) ? gridToWorld.getScaleY()
297: : gridToWorld.getShearX();
298: final double xLoc = gridToWorld.getTranslateX();
299: final double yLoc = gridToWorld.getTranslateY();
300:
301: // /////////////////////////////////////////////////////////////////////
302: //
303: // writing world file
304: //
305: // ////////////////////////////////////////////////////////////////////
306: final StringBuffer buff = new StringBuffer(baseFile);
307: // looking for another extension
308: final Set ext = WorldImageFormat
309: .getWorldExtension(this .extension);
310: final Iterator it = ext.iterator();
311: if (!it.hasNext())
312: throw new DataSourceException("Unable to parse extension "
313: + extension);
314: buff.append((String) it.next());
315: final File worldFile = new File(buff.toString());
316: final PrintWriter out = new PrintWriter(new FileOutputStream(
317: worldFile));
318: out.println(xPixelSize);
319: out.println(rotation1);
320: out.println(rotation2);
321: out.println(yPixelSize);
322: out.println(xLoc);
323: out.println(yLoc);
324: out.flush();
325: out.close();
326:
327: }
328:
329: /**
330: * Encode the given coverage to the requsted output format.
331: *
332: * @param sourceCoverage
333: * the coverage to be encoded.s
334: * @param outstream
335: * OutputStream
336: * @throws IOException
337: *
338: * @throws IOException
339: * @throws IllegalArgumentException
340: * DOCUMENT ME!
341: */
342: private void encode(final GridCoverage2D sourceCoverage,
343: final ImageOutputStream outstream) throws IOException {
344:
345: // do we have a source coverage?
346: if (sourceCoverage == null) {
347: throw new IllegalArgumentException(
348: "A coverage must be provided in order for write to succeed!");
349: }
350:
351: /**
352: * Getting the non geophysics view of this grid coverage. the
353: * geophysiscs view usually comes with an index color model for 3 bands,
354: * since sometimes I get some problems with JAI encoders I select only
355: * the first band, which by the way is the only band we use.
356: */
357: RenderedImage image = (sourceCoverage).geophysics(false)
358: .getRenderedImage();
359: final ImageWorker worker = new ImageWorker(image);
360:
361: // /////////////////////////////////////////////////////////////////////
362: //
363: // With index color model we want just the first band
364: //
365: // /////////////////////////////////////////////////////////////////////
366: if (image.getColorModel() instanceof IndexColorModel
367: && (image.getSampleModel().getNumBands() > 1)) {
368: worker.retainBands(1);
369: image = worker.getRenderedImage();
370: }
371:
372: /**
373: * For the moment we do not work with DirectColorModel but instead we
374: * switch to component color model which is really easier to handle even
375: * if it much more memory expensive. Once we are in component color
376: * model is really easy to go to Gif and similar.
377: */
378: if (image.getColorModel() instanceof DirectColorModel) {
379: worker.forceComponentColorModel();
380: image = worker.getRenderedImage();
381: }
382:
383: /**
384: * ADJUSTMENTS FOR VARIOUS FILE FORMATS
385: */
386:
387: // ------------------------GIF-----------------------------------
388: if (extension.compareToIgnoreCase("gif") == 0) {
389:
390: /**
391: * IndexColorModel with more than 8 bits for sample might be a
392: * problem because GIF allows only 8 bits based palette therefore I
393: * prefere switching to component color model in order to handle
394: * this properly. NOTE. The only transfert types avalaible for
395: * IndexColorModel are byte and ushort.
396: */
397: if (image.getColorModel() instanceof IndexColorModel
398: && (image.getSampleModel().getTransferType() != DataBuffer.TYPE_BYTE)) {
399: worker.forceComponentColorModel();
400: image = worker.getRenderedImage();
401: }
402:
403: /**
404: * component color model is not well digested by the gif encoder we
405: * need to go to indecolor model somehow. This code for the moment
406: * remove transparency, but I am confident I will find a way to add
407: * that.
408: */
409: if (image.getColorModel() instanceof ComponentColorModel) {
410: worker.forceIndexColorModelForGIF(true);
411: image = worker.getRenderedImage();
412: } else
413: /**
414: * IndexColorModel with full transparency support is not suitable
415: * for gif images we need to go to bitmask loosing some
416: * informations. we have only one full transparent color.
417: */
418: if (image.getColorModel() instanceof IndexColorModel) {
419: worker.forceIndexColorModelForGIF(true);
420: image = worker.getRenderedImage();
421: }
422: }
423: // else
424: // -----------------TIFF--------------------------------------
425:
426: // /**
427: // * TIFF file format. We need just a couple of correction for this
428: // * format. It seems that the encoder does not work fine with
429: // * IndexColorModel therefore in such a case we need the reformat the
430: // * input image to a ComponentColorModel.
431: // */
432: // if (extension.compareToIgnoreCase("tiff") == 0
433: // || extension.compareToIgnoreCase("tif") == 0) {
434: // // Are we dealing with IndexColorModel? If so we need to go back
435: // // to ComponentColorModel
436: // if (image.getColorModel() instanceof IndexColorModel) {
437: // surrogateImage = ImageUtilities
438: // .reformatColorModel2ComponentColorModel(surrogateImage);
439: // }
440: // }
441:
442: /**
443: * write using JAI encoders
444: */
445: final ParameterBlockJAI pbjImageWrite = new ParameterBlockJAI(
446: "ImageWrite");
447: pbjImageWrite.addSource(image);
448: pbjImageWrite.setParameter("Output", outstream);
449: pbjImageWrite.setParameter("VerifyOutput", Boolean.FALSE);
450: pbjImageWrite.setParameter("Format", extension);
451: JAI.create("ImageWrite", pbjImageWrite);
452: outstream.flush();
453: outstream.close();
454:
455: }
456: }
|