001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, 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: package org.geotools.gce.geotiff;
017:
018: import java.awt.geom.AffineTransform;
019: import java.awt.image.RenderedImage;
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.OutputStream;
023: import java.net.URL;
024: import java.net.URLDecoder;
025:
026: import javax.imageio.IIOException;
027: import javax.imageio.IIOImage;
028: import javax.imageio.ImageIO;
029: import javax.imageio.ImageTypeSpecifier;
030: import javax.imageio.ImageWriteParam;
031: import javax.imageio.ImageWriter;
032: import javax.imageio.metadata.IIOInvalidTreeException;
033: import javax.imageio.metadata.IIOMetadata;
034: import javax.imageio.stream.FileCacheImageOutputStream;
035: import javax.imageio.stream.ImageOutputStream;
036:
037: import org.geotools.coverage.grid.GridCoverage2D;
038: import org.geotools.coverage.grid.io.AbstractGridCoverageWriter;
039: import org.geotools.coverage.grid.io.AbstractGridFormat;
040: import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams;
041: import org.geotools.factory.Hints;
042: import org.geotools.gce.geotiff.IIOMetadataAdpaters.GeoTiffIIOMetadataEncoder;
043: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.GeoTiffConstants;
044: import org.geotools.gce.geotiff.crs_adapters.CRS2GeoTiffMetadataAdapter;
045: import org.geotools.parameter.Parameter;
046: import org.geotools.referencing.operation.matrix.XAffineTransform;
047: import org.jdom.Document;
048: import org.jdom.Element;
049: import org.jdom.JDOMException;
050: import org.jdom.Parent;
051: import org.jdom.input.DOMBuilder;
052: import org.jdom.output.DOMOutputter;
053: import org.opengis.coverage.grid.Format;
054: import org.opengis.coverage.grid.GridCoverage;
055: import org.opengis.coverage.grid.GridCoverageWriter;
056: import org.opengis.coverage.grid.GridGeometry;
057: import org.opengis.coverage.grid.GridRange;
058: import org.opengis.parameter.GeneralParameterValue;
059: import org.opengis.referencing.crs.CoordinateReferenceSystem;
060: import org.opengis.referencing.crs.GeographicCRS;
061: import org.opengis.referencing.crs.ProjectedCRS;
062: import org.opengis.referencing.operation.TransformException;
063:
064: import com.sun.media.imageioimpl.plugins.tiff.TIFFImageMetadata;
065: import com.sun.media.imageioimpl.plugins.tiff.TIFFImageWriterSpi;
066:
067: /**
068: * @author Simone Giannecchini
069: * @source $URL:
070: * http://svn.geotools.org/geotools/trunk/gt/plugin/geotiff/src/org/geotools/gce/geotiff/GeoTiffWriter.java $
071: */
072: public final class GeoTiffWriter extends AbstractGridCoverageWriter
073: implements GridCoverageWriter {
074:
075: /** factory for getting tiff writers. */
076: private final static TIFFImageWriterSpi tiffWriterFactory = new TIFFImageWriterSpi();
077:
078: /**
079: * Constructor for a {@link GeoTiffWriter}.
080: *
081: * @param destination
082: * @throws IOException
083: */
084: public GeoTiffWriter(Object destination) throws IOException {
085: this (destination, null);
086:
087: }
088:
089: /**
090: * Constructor for a {@link GeoTiffWriter}.
091: *
092: * @param destination
093: * @param hints
094: * @throws IOException
095: */
096: public GeoTiffWriter(Object destination, Hints hints)
097: throws IOException {
098:
099: this .destination = destination;
100: if (destination instanceof File)
101: this .outStream = ImageIO
102: .createImageOutputStream(destination);
103: else if (destination instanceof URL) {
104: final URL dest = (URL) destination;
105: if (dest.getProtocol().equalsIgnoreCase("file")) {
106: final File destFile = new File(URLDecoder.decode(dest
107: .getFile(), "UTF-8"));
108: this .outStream = ImageIO
109: .createImageOutputStream(destFile);
110: }
111:
112: } else if (destination instanceof OutputStream) {
113:
114: this .outStream = ImageIO
115: .createImageOutputStream((OutputStream) destination);
116:
117: } else if (destination instanceof ImageOutputStream)
118: this .outStream = (ImageOutputStream) destination;
119: else
120: throw new IllegalArgumentException(
121: "The provided destination canno be used!");
122: // //
123: //
124: // managing hints
125: //
126: // //
127: if (hints != null) {
128: if (super .hints == null)
129: this .hints = new Hints(null);
130: hints.add(hints);
131: }
132:
133: }
134:
135: /*
136: * (non-Javadoc)
137: *
138: * @see org.opengis.coverage.grid.GridCoverageWriter#getFormat()
139: */
140: public Format getFormat() {
141: return new GeoTiffFormat();
142: }
143:
144: /*
145: * (non-Javadoc)
146: *
147: * @see org.opengis.coverage.grid.GridCoverageWriter#write(org.opengis.coverage.grid.GridCoverage,
148: * org.opengis.parameter.GeneralParameterValue[])
149: */
150: public void write(final GridCoverage gc,
151: final GeneralParameterValue[] params)
152: throws IllegalArgumentException, IOException,
153: IndexOutOfBoundsException {
154:
155: GeoToolsWriteParams gtParams = null;
156: if (params != null) {
157: // /////////////////////////////////////////////////////////////////////
158: //
159: // Checking params
160: //
161: // /////////////////////////////////////////////////////////////////////
162: if (params != null) {
163: Parameter param;
164: final int length = params.length;
165: for (int i = 0; i < length; i++) {
166: param = (Parameter) params[i];
167: if (param
168: .getDescriptor()
169: .getName()
170: .getCode()
171: .equals(
172: AbstractGridFormat.GEOTOOLS_WRITE_PARAMS
173: .getName().toString())) {
174: gtParams = (GeoToolsWriteParams) param
175: .getValue();
176: }
177: }
178: }
179: }
180: if (gtParams == null)
181: gtParams = new GeoTiffWriteParams();
182:
183: // /////////////////////////////////////////////////////////////////////
184: //
185: // getting the coordinate reference system
186: //
187: // /////////////////////////////////////////////////////////////////////
188: final CoordinateReferenceSystem crs = gc
189: .getCoordinateReferenceSystem();
190:
191: // /////////////////////////////////////////////////////////////////////
192: //
193: // we handle just projected andgeographic crs
194: //
195: // /////////////////////////////////////////////////////////////////////
196: if (crs instanceof ProjectedCRS || crs instanceof GeographicCRS) {
197:
198: // creating geotiff metadata
199: final CRS2GeoTiffMetadataAdapter adapter = (CRS2GeoTiffMetadataAdapter) CRS2GeoTiffMetadataAdapter
200: .get(crs);
201: final GeoTiffIIOMetadataEncoder metadata = adapter
202: .parseCoordinateReferenceSystem();
203:
204: // setting georeferencing
205: final GridGeometry gg = gc.getGridGeometry();
206: final GridRange range = gg.getGridRange();
207: final AffineTransform tr = (AffineTransform) gg
208: .getGridToCRS();
209: setGeoReference(crs, metadata, tr, range);
210:
211: // writing ALWAYS the geophysics vew of the data
212: writeImage(((GridCoverage2D) gc).geophysics(true)
213: .getRenderedImage(), this .outStream, metadata,
214: gtParams);
215:
216: } else
217: throw new GeoTiffException(
218: null,
219: "The supplied grid coverage uses an unsupported crs! You are allowed to use only projected and geographic coordinate reference systems",
220: null);
221: }
222:
223: /**
224: * This method is used to set the tie point and the scale parameters for the
225: * GeoTiff file we are writing or the ModelTransformation in case a more
226: * general {@link AffineTransform} is needed to represent the raster space
227: * to model space transform.
228: *
229: * <p>
230: * This method works regardles of the nature fo the crs without making any
231: * assumptions on the order or the direction of the axes, but checking them
232: * from the supplied CRS.
233: *
234: * @see {@link http://lists.maptools.org/pipermail/geotiff/2006-January/000213.html}
235: * @see {@http://lists.maptools.org/pipermail/geotiff/2006-January/000212.html}
236: * @param crs
237: * The {@link CoordinateReferenceSystem} of the
238: * {@link GridCoverage2D} to encode.
239: * @param metadata
240: * where to set the georeferencing information.
241: * @param range
242: * that describes the raster space for this geotiff.
243: * @param rasterToModel
244: * describes the {@link AffineTransform} between raster space and
245: * model space.
246: *
247: * @throws IndexOutOfBoundsException
248: * @throws IOException
249: * @throws TransformException
250: */
251: private void setGeoReference(final CoordinateReferenceSystem crs,
252: final GeoTiffIIOMetadataEncoder metadata,
253: final AffineTransform rasterToModel, GridRange range)
254: throws IndexOutOfBoundsException, IOException {
255:
256: // /////////////////////////////////////////////////////////////////////
257: //
258: // We have to set an affine transformation which is going to be 2D
259: // since we support baseline GeoTiff.
260: //
261: // /////////////////////////////////////////////////////////////////////
262: AffineTransform modifiedRasterToModel;
263: int minx = range.getLower(0), miny = range.getLower(1);
264: if (minx != 0 || miny != 0) {
265: // //
266: //
267: // Preconcatenate a transform to have raster space beginning at
268: // (0,0)
269: //
270: // //
271: modifiedRasterToModel = new AffineTransform(rasterToModel);
272: modifiedRasterToModel.concatenate(AffineTransform
273: .getTranslateInstance(minx, miny));
274: } else
275: modifiedRasterToModel = rasterToModel;
276:
277: // /////////////////////////////////////////////////////////////////////
278: //
279: // Setting raster type to pixel centre since the ogc specifications
280: // require so.
281: //
282: // /////////////////////////////////////////////////////////////////////
283: metadata.addGeoShortParam(GeoTiffConstants.GTRasterTypeGeoKey,
284: GeoTiffConstants.RasterPixelIsPoint);
285:
286: // /////////////////////////////////////////////////////////////////////
287: //
288: // AXES DIRECTION
289: //
290: // we need to understand how the axes of this gridcoverage are
291: // specified, trying to understand the direction of the first axis in
292: // order to correctly use transformations.
293: //
294: // Note that here wew assume that in case of a Flip the flip is on the Y
295: // axis.
296: //
297: // /////////////////////////////////////////////////////////////////////
298: boolean lonFirst = XAffineTransform.getSwapXY(rasterToModel) != -1;
299:
300: // /////////////////////////////////////////////////////////////////////
301: //
302: // ROTATION
303: //
304: // If fthere is not rotation or shearing or flipping we have a simple
305: // scale and translate hence we can simply set the tie points.
306: //
307: // /////////////////////////////////////////////////////////////////////
308: double rotation = XAffineTransform.getRotation(rasterToModel);
309:
310: // /////////////////////////////////////////////////////////////////////
311: //
312: // Deciding how to save the georef with respect to the CRS.
313: //
314: // /////////////////////////////////////////////////////////////////////
315: // tie points
316: if (!(Double.isInfinite(rotation) || Double.isNaN(rotation) || Math
317: .abs(rotation) > 1E-6)) {
318: final double tiePointLongitude = (lonFirst) ? rasterToModel
319: .getTranslateX() : rasterToModel.getTranslateY();
320: final double tiePointLatitude = (lonFirst) ? rasterToModel
321: .getTranslateY() : rasterToModel.getTranslateX();
322: metadata.setModelTiePoint(0, 0, 0, tiePointLongitude,
323: tiePointLatitude, 0);
324: // scale
325: final double scaleModelToRasterLongitude = (lonFirst) ? Math
326: .abs(rasterToModel.getScaleX())
327: : Math.abs(rasterToModel.getShearY());
328: final double scaleModelToRasterLatitude = (lonFirst) ? Math
329: .abs(rasterToModel.getScaleY()) : Math
330: .abs(rasterToModel.getShearX());
331: metadata.setModelPixelScale(scaleModelToRasterLongitude,
332: scaleModelToRasterLatitude, 0);
333: // Alternative code, not yet enabled in order to avoid breaking
334: // code.
335: // The following code is insensitive to axis order and rotations in
336: // the
337: // 'coord' space (not in the 'grid' space, otherwise we would not
338: // take
339: // the inverse of the matrix).
340: /*
341: * final AffineTransform coordToGrid = gridToCoord.createInverse();
342: * final double scaleModelToRasterLongitude = 1 /
343: * XAffineTransform.getScaleX0(coordToGrid); final double
344: * scaleModelToRasterLatitude = 1 /
345: * XAffineTransform.getScaleY0(coordToGrid);
346: */
347: } else {
348:
349: metadata.setModelTransformation(modifiedRasterToModel);
350:
351: }
352: }
353:
354: /**
355: * Writes the provided rendered image to the provided image output stream
356: * using the supplied geotiff metadata.
357: *
358: * @param gtParams
359: */
360: private boolean writeImage(final RenderedImage image,
361: final ImageOutputStream outputStream,
362: final GeoTiffIIOMetadataEncoder geoTIFFMetadata,
363: GeoToolsWriteParams gtParams) throws IOException {
364: if (image == null || outputStream == null) {
365: throw new IllegalArgumentException(
366: "some parameters are null");
367: }
368: final ImageWriteParam params = gtParams.getAdaptee();
369: // /////////////////////////////////////////////////////////////////////
370: //
371: // GETTING READER AND METADATA
372: //
373: // /////////////////////////////////////////////////////////////////////
374: final ImageWriter writer = tiffWriterFactory
375: .createWriterInstance();
376: final IIOMetadata metadata = createGeoTiffIIOMetadata(writer,
377: ImageTypeSpecifier.createFromRenderedImage(image),
378: geoTIFFMetadata, params);
379:
380: // /////////////////////////////////////////////////////////////////////
381: //
382: // IMAGEWRITE
383: //
384: // /////////////////////////////////////////////////////////////////////
385: writer.setOutput(outputStream);
386: writer.write(writer.getDefaultStreamMetadata(params),
387: new IIOImage(image, null, metadata), params);
388:
389: // /////////////////////////////////////////////////////////////////////
390: //
391: // release resources
392: //
393: // /////////////////////////////////////////////////////////////////////
394: outputStream.flush();
395: if (!(destination instanceof ImageOutputStream))
396: outputStream.close();
397: writer.dispose();
398:
399: return true;
400: }
401:
402: /**
403: * Creates image metadata which complies to the GeoTIFFWritingUtilities
404: * specification for the given image writer, image type and
405: * GeoTIFFWritingUtilities metadata.
406: *
407: * @param writer
408: * the image writer, must not be null
409: * @param type
410: * the image type, must not be null
411: * @param geoTIFFMetadata
412: * the GeoTIFFWritingUtilities metadata, must not be null
413: * @param params
414: * @return the image metadata, never null
415: * @throws IIOException
416: * if the metadata cannot be created
417: */
418: public final static IIOMetadata createGeoTiffIIOMetadata(
419: ImageWriter writer, ImageTypeSpecifier type,
420: GeoTiffIIOMetadataEncoder geoTIFFMetadata,
421: ImageWriteParam params) throws IIOException {
422: IIOMetadata imageMetadata = writer.getDefaultImageMetadata(
423: type, params);
424: imageMetadata = writer.convertImageMetadata(imageMetadata,
425: type, params);
426: org.w3c.dom.Element w3cElement = (org.w3c.dom.Element) imageMetadata
427: .getAsTree(GeoTiffConstants.GEOTIFF_IIO_METADATA_FORMAT_NAME);
428: final Element element = new DOMBuilder().build(w3cElement);
429:
430: geoTIFFMetadata.assignTo(element);
431:
432: final Parent parent = element.getParent();
433: parent.removeContent(element);
434:
435: final Document document = new Document(element);
436:
437: try {
438: final org.w3c.dom.Document w3cDoc = new DOMOutputter()
439: .output(document);
440:
441: /*
442: * DOMSerializerImpl domSerializer = new DOMSerializerImpl();
443: * System.out.println(domSerializer.writeToString(w3cDoc.getFirstChild()));
444: */
445:
446: final IIOMetadata iioMetadata = new TIFFImageMetadata(
447: TIFFImageMetadata.parseIFD(w3cDoc
448: .getDocumentElement().getFirstChild()));
449: imageMetadata = iioMetadata;
450: } catch (JDOMException e) {
451: throw new IIOException(
452: "Failed to set GeoTIFFWritingUtilities specific tags.",
453: e);
454: } catch (IIOInvalidTreeException e) {
455: throw new IIOException(
456: "Failed to set GeoTIFFWritingUtilities specific tags.",
457: e);
458: }
459:
460: return imageMetadata;
461: }
462: }
|