/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2003-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.gce.gtopo30;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.Histogram;
import javax.media.jai.ImageLayout;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverageWriter;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams;
import org.geotools.coverage.processing.operation.Resample;
import org.geotools.coverage.processing.operation.SelectSampleDimension;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.factory.Hints;
import org.geotools.parameter.Parameter;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.util.NumberRange;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import com.sun.media.jai.operator.ImageWriteDescriptor;
/**
* Class used for encoding a {@link GridCoverage2D} into a GTOPO30 file.
*
*
* @author Simone Giannecchini
* @author jeichar
* @author mkraemer
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/plugin/gtopo30/src/org/geotools/gce/gtopo30/GTopo30Writer.java $
*/
final public class GTopo30Writer extends AbstractGridCoverageWriter implements
GridCoverageWriter {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.gce.gtopo30");
static {
// register new JAI operation
NoDataReplacerOpImage.register(JAI.getDefaultInstance());
}
/** Cached factory for a {@link SelectSampleDimension} operation. */
private final static SelectSampleDimension sdFactory = new SelectSampleDimension();
/** Cached factory for {@link Resample} operation. */
private final static Resample resampleFactory = new Resample();
/**
* Standard width for the GIF image.
*/
private final static int GIF_WIDTH = 640;
/** Standard height for the GIF image. */
private final static int GIF_HEIGHT = 480;
/**
* Creates a {@link GTopo30Writer}.
*
* @param dest
* The destination object can be a {@link File} (a directory
* actually), an {@link URL} to a directory, or a String
* representing a directory or an {@link URL} to a directory.
* @throws DataSourceException
*/
public GTopo30Writer(final Object dest) throws DataSourceException {
this(dest, null);
}
/**
* Creates a {@link GTopo30Writer}.
*
* @param dest
* The destination object can be a File (a directory actually),
* an URL to a directory, or a String representing a directory or
* an URL to a directory.
* @throws DataSourceException
*/
public GTopo30Writer(final Object dest, final Hints hints)
throws DataSourceException {
// /////////////////////////////////////////////////////////////////////
//
// Initial checks
//
// /////////////////////////////////////////////////////////////////////
if (dest == null) {
throw new NullPointerException("The provided destination is null.");
}
destination = dest;
final File temp;
final URL url;
// we only accept a directory as a path
if (dest instanceof String) {
temp = new File((String) dest);
// if it exists and it is not a directory that 's not good
if ((temp.exists() && !temp.isDirectory()) || !temp.exists()) {
destination = null; // we cannot write
} else if (!temp.exists()) {
// well let's create it!
if (!temp.mkdir()) {
destination = null;
} else {
destination = temp.getAbsolutePath();
}
}
} else if (dest instanceof File) {
temp = (File) dest;
if (temp.exists() && !temp.isDirectory()) {
this.destination = null;
} else if (!temp.exists()) {
// let's create it
if (temp.mkdir()) {
destination = temp.getAbsolutePath();
} else {
destination = null;
}
}
} else if (dest instanceof URL) {
url = (URL) dest;
if (url.getProtocol().compareToIgnoreCase("file") != 0) {
destination = null;
}
temp = DataUtilities.urlToFile(url);
if (temp.exists() && !temp.isDirectory()) {
destination = null;
} else if (!temp.exists()) {
// let's create it
if (temp.mkdir()) {
destination = temp.getAbsolutePath();
} else {
destination = null;
}
}
} else if (dest instanceof ImageOutputStream) {
this.destination = dest;
} else {
throw new IllegalArgumentException(
"The provided destination is of an incorrect type.");
}
// /////////////////////////////////////////////////////////////////////
//
// managing hints
//
// /////////////////////////////////////////////////////////////////////
// //
//
// managing hints
//
// //
if (this.hints == null)
this.hints= new Hints();
if (hints != null) {
// prevent the use from reordering axes
this.hints.add(hints);
}
}
/**
* @see org.opengis.coverage.grid.GridCoverageWriter#getFormat()
*/
@SuppressWarnings("deprecation")
public Format getFormat() {
return new GTopo30Format();
}
/**
* @see org.opengis.coverage.grid.GridCoverageWriter#write(org.opengis.coverage.grid.GridCoverage,
* org.opengis.parameter.GeneralParameterValue[])
*/
public void write(final GridCoverage coverage,
final GeneralParameterValue[] params)
throws java.lang.IllegalArgumentException, java.io.IOException {
// /////////////////////////////////////////////////////////////////////
//
// Checking input params
//
// /////////////////////////////////////////////////////////////////////
if (coverage == null)
throw new NullPointerException(
"The provided source coverage is null");
// the source GridCoverage2D
GridCoverage2D gc2D = (GridCoverage2D) coverage;
// /////////////////////////////////////////////////////////////////////
//
// Checking writing params
//
// /////////////////////////////////////////////////////////////////////
GeoToolsWriteParams gtParams = null;
if (params != null) {
if (params != null) {
Parameter<?> param;
final int length = params.length;
for (int i = 0; i < length; i++) {
param = (Parameter<?>) params[i];
if (param.getDescriptor().getName().getCode().equals(
AbstractGridFormat.GEOTOOLS_WRITE_PARAMS.getName()
.toString())) {
gtParams = (GeoToolsWriteParams) param.getValue();
}
}
}
}
if (gtParams == null)
gtParams = new GTopo30WriteParams();
// compression
final boolean compressed = gtParams.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT;
// write band
int[] writeBands = gtParams.getSourceBands();
int writeBand = CoverageUtilities.getVisibleBand(gc2D
.getRenderedImage());
if ((writeBands == null || writeBands.length == 0 || writeBands.length > 1)
&& (writeBand < 0 || writeBand > gc2D.getNumSampleDimensions()))
throw new IllegalArgumentException(
"You need to supply a valid index for deciding which band to write.");
if (!((writeBands == null || writeBands.length == 0 || writeBands.length > 1)))
writeBand = writeBands[0];
// destination file name
String fileName = gc2D.getName().toString();
// destination XXX HAS to be a dir
if (compressed) {
destination = new ZipOutputStream(new BufferedOutputStream(
new FileOutputStream(new File((File) destination,
new StringBuffer(fileName).append(".zip")
.toString()))));
}
// /////////////////////////////////////////////////////////////////////
//
// STEP 1
//
// We might need to do a band select in order to cope with the GTOPO30
// limitation.
//
// /////////////////////////////////////////////////////////////////////
final ParameterValueGroup pvg = sdFactory.getParameters();
pvg.parameter("Source").setValue(gc2D);
pvg.parameter("SampleDimensions").setValue(new int[]{writeBand});
pvg.parameter("VisibleSampleDimension").setValue(writeBand);
gc2D = (GridCoverage2D) sdFactory.doOperation(pvg, hints);
// /////////////////////////////////////////////////////////////////////
//
// STEP 2
//
// We might need to reformat the selected band for this coverage in
// order to cope with the GTOPO30 limitation.
//
// /////////////////////////////////////////////////////////////////////
final PlanarImage reFormattedData2Short = reFormatCoverageImage(gc2D,
DataBuffer.TYPE_SHORT);
// /////////////////////////////////////////////////////////////////////
//
// STEP 2
//
// Start with writing things out.
//
// /////////////////////////////////////////////////////////////////////
// //
//
// write DEM
//
// //
this.writeDEM(reFormattedData2Short, fileName, destination);
// //
//
// write statistics
//
// //
this.writeStats(reFormattedData2Short, fileName, destination, gc2D);
// we won't use this image anymore let's release the resources.
// //
//
// write world file
//
// //
this.writeWorldFile(gc2D, fileName, destination);
// //
//
// write projection
//
// //
this.writePRJ(gc2D, fileName, destination);
// //
//
// write HDR
//
// //
this.writeHDR(gc2D, fileName, destination);
// //
//
// write gif
//
// //
this.writeGIF(gc2D, fileName, destination);
// //
//
// write src
//
// //
this.writeSRC(gc2D, fileName, destination);
if (compressed) {
((ZipOutputStream) destination).close();
}
}
/**
* Reformats the input single banded planar image to having
* {@link DataBuffer#TYPE_SHORT} sample type as requested by the GTOPO30
* format.
*
* @param gc2D
* source {@link GridCoverage2D} from which to take the input
* {@link PlanarImage}.
*
* @return the reformated {@link PlanarImage}.
*/
private PlanarImage reFormatCoverageImage(final GridCoverage2D gc2D,
int writeBand) {
// internal image
PlanarImage image = (PlanarImage) gc2D.getRenderedImage();
// sample dimension type
final int origDataType = image.getSampleModel().getDataType();
// short?
if (DataBuffer.TYPE_SHORT == origDataType) {
return image;
}
final GridSampleDimension visibleSD = ((GridSampleDimension) gc2D
.getSampleDimension(0)).geophysics(true);
// getting categories
final List oldCategories = visibleSD.getCategories();
// removing old nodata category
// candidate
Category candidate = null;
NumberRange candidateRange = null;
final Iterator it = oldCategories.iterator();
while (it.hasNext()) {
candidate = (Category) it.next();
// removing candidate for NaN
if (candidate.getName().toString().equalsIgnoreCase("no data")) {
candidateRange = candidate.getRange();
break;
}
}
// new no data category
final double oldNoData = candidateRange.getMinimum();
final ParameterBlockJAI pbjM = new ParameterBlockJAI(
"org.geotools.gce.gtopo30.NoDataReplacer");
pbjM.addSource(image);
pbjM.setParameter("oldNoData", oldNoData);
image = JAI.create("org.geotools.gce.gtopo30.NoDataReplacer", pbjM,
hints);
return image;
}
/**
* Writing down the header file for the gtopo30 format:
*
* @param coverage
* The GridCoverage to write
* @param file
* The destination object (can be a File or ZipOutputStream)
*
* @throws IOException
* If the file could not be written
*/
private void writeHDR(final GridCoverage2D gc, final String name,
final Object dest) throws IOException {
// final GeneralEnvelope envelope = (GeneralEnvelope) gc.getEnvelope();
final double noData = -9999.0;
// checking the directions of the axes.
// we need to understand how the axes of this gridcoverage are
// specified
/*
* Note from Martin: I suggest to replace all the above lines by the commented code below.
* The change need to be tested in order to make sure that I didn't made a mistake in the
* mathematic. Note that 'lonFirst' totally vanish.
*/
final AffineTransform gridToWorld = (AffineTransform) gc
.getGridGeometry().getGridToCRS2D();
boolean lonFirst = (XAffineTransform.getSwapXY(gridToWorld) != -1);
final double geospatialDx = Math.abs((lonFirst) ? gridToWorld
.getScaleX() : gridToWorld.getShearY());
final double geospatialDy = Math.abs((lonFirst) ? gridToWorld
.getScaleY() : gridToWorld.getShearX());
// getting corner coordinates of the left upper corner
final double xUpperLeft = lonFirst ? gridToWorld.getTranslateX()
: gridToWorld.getTranslateY();
final double yUpperLeft = lonFirst ? gridToWorld.getTranslateY()
: gridToWorld.getTranslateX();
/*
final AffineTransform worldToGrid = (AffineTransform) gc
.getGridGeometry().getGridToCoordinateSystem().inverse();
final double geospatialDx = 1 / XAffineTransform.getScaleX0(worldToGrid);
final double geospatialDy = 1 / XAffineTransform.getScaleY0(worldToGrid);
// getting corner coordinates of the left upper corner
final double xUpperLeft = -worldToGrid.getTranslateX() * geospatialDx;
final double yUpperLeft = -worldToGrid.getTranslateY() * geospatialDy;
*/
// calculating the physical resolution over x and y.
final int geometryWidth = gc.getGridGeometry().getGridRange().getSpan(0);
final int geometryHeight = gc.getGridGeometry().getGridRange().getSpan(1);
if (dest instanceof File) {
final PrintWriter out = new PrintWriter(
new BufferedOutputStream(new FileOutputStream(new File(
(File) dest, new StringBuffer(name).append(
".HDR").toString()))));
// output header and assign header fields
out.print("BYTEORDER");
out.print(" ");
out.println("M");
out.print("LAYOUT");
out.print(" ");
out.println("BIL");
out.print("NROWS");
out.print(" ");
out.println(geometryHeight);
out.print("NCOLS");
out.print(" ");
out.println(geometryWidth);
out.print("NBANDS");
out.print(" ");
out.println("1");
out.print("NBITS");
out.print(" ");
out.println("16");
out.print("BANDROWBYTES");
out.print(" ");
out.println(geometryWidth * 2);
out.print("TOTALROWBYTES");
out.print(" ");
out.println(geometryWidth * 2);
out.print("BANDGAPBYTES");
out.print(" ");
out.println(0);
out.print("NODATA");
out.print(" ");
out.println((int) noData);
out.print("ULXMAP");
out.print(" ");
out.println(xUpperLeft);
out.print("ULYMAP");
out.print(" ");
out.println(yUpperLeft);
out.print("XDIM");
out.print(" ");
out.println(geospatialDx);
out.print("YDIM");
out.print(" ");
out.println(geospatialDy);
out.flush();
out.close();
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(gc.getName().toString()
+ ".HDR");
outZ.putNextEntry(e);
// writing world file
outZ.write("BYTEORDER".getBytes());
outZ.write(" ".getBytes());
outZ.write("M".getBytes());
outZ.write("\n".getBytes());
outZ.write("LAYoutZ".getBytes());
outZ.write(" ".getBytes());
outZ.write("BIL".getBytes());
outZ.write("\n".getBytes());
outZ.write("NROWS".getBytes());
outZ.write(" ".getBytes());
outZ.write(Integer.toString(geometryHeight).getBytes());
outZ.write("\n".getBytes());
outZ.write("NCOLS".getBytes());
outZ.write(" ".getBytes());
outZ.write(Integer.toString(geometryWidth).getBytes());
outZ.write("\n".getBytes());
outZ.write("NBANDS".getBytes());
outZ.write(" ".getBytes());
outZ.write("1".getBytes());
outZ.write("\n".getBytes());
outZ.write("NBITS".getBytes());
outZ.write(" ".getBytes());
outZ.write("16".getBytes());
outZ.write("\n".getBytes());
outZ.write("BANDROWBYTES".getBytes());
outZ.write(" ".getBytes());
outZ.write(Integer.toString(geometryWidth * 2).getBytes());
outZ.write("\n".getBytes());
outZ.write("TOTALROWBYTES".getBytes());
outZ.write(" ".getBytes());
outZ.write(Integer.toString(geometryWidth * 2).getBytes());
outZ.write("\n".getBytes());
outZ.write("BANDGAPBYTES".getBytes());
outZ.write(" ".getBytes());
outZ.write("0".getBytes());
outZ.write("\n".getBytes());
outZ.write("NODATA".getBytes());
outZ.write(" ".getBytes());
outZ.write(Integer.toString((int) noData).getBytes());
outZ.write("\n".getBytes());
outZ.write("ULXMAP".getBytes());
outZ.write(" ".getBytes());
outZ.write(Double.toString(xUpperLeft + (geospatialDx / 2))
.getBytes());
outZ.write("\n".getBytes());
outZ.write("ULYMAP".getBytes());
outZ.write(" ".getBytes());
outZ.write(Double.toString(yUpperLeft - (geospatialDy / 2))
.getBytes());
outZ.write("\n".getBytes());
outZ.write("XDIM".getBytes());
outZ.write(" ".getBytes());
outZ.write(Double.toString(geospatialDx).getBytes());
outZ.write("\n".getBytes());
outZ.write("YDIM".getBytes());
outZ.write(" ".getBytes());
outZ.write(Double.toString(geospatialDy).toString().getBytes());
outZ.write("\n".getBytes());
outZ.closeEntry();
((ZipOutputStream) dest).closeEntry();
}
}
/**
* Writes the source file (.SRC). The default byte order is BIG_ENDIAN.
*
* @param gc
* The GridCoverage to write
* @param file
* The destination object (can be a File or ZipOutputStream)
* @param dest
*
* @throws FileNotFoundException
* If the destination file could not be found
* @throws IOException
* If the file could not be written
*/
private void writeSRC(GridCoverage2D gc, final String name, Object dest)
throws FileNotFoundException, IOException {
// /////////////////////////////////////////////////////////////////////
// TODO @task @todo
// Here I am making the assumption that the non geophysiscs view is 8
// bit but it can also be 16. I should do something more general like a
// clamp plus a format but for the moment this is enough.
//
// We need also to get the one visible band
//
// /////////////////////////////////////////////////////////////////////
gc = gc.geophysics(false);
ImageOutputStream out = null;
if (dest instanceof File) {
out = ImageIO.createImageOutputStream(new File((File) dest,
new StringBuffer(name).append(".SRC").toString()));
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(gc.getName().toString() + ".SRC");
outZ.putNextEntry(e);
out = ImageIO.createImageOutputStream(outZ);
}
// setting byte order
out.setByteOrder(java.nio.ByteOrder.BIG_ENDIAN);
// /////////////////////////////////////////////////////////////////////
//
// Prepare to write
//
// /////////////////////////////////////////////////////////////////////
RenderedImage image = gc.getRenderedImage();
image = untileImage(image);
final ParameterBlockJAI pbj = new ParameterBlockJAI("imagewrite");
pbj.addSource(image);
pbj.setParameter("Format", "raw");
pbj.setParameter("Output", out);
final RenderedOp wOp = JAI.create("ImageWrite", pbj);
// /////////////////////////////////////////////////////////////////////
//
// Dispose things
//
// /////////////////////////////////////////////////////////////////////
final Object o = wOp
.getProperty(ImageWriteDescriptor.PROPERTY_NAME_IMAGE_WRITER);
if (o instanceof ImageWriter)
((ImageWriter) o).dispose();
if (!(dest instanceof File)) {
((ZipOutputStream) dest).closeEntry();
}
out.flush();
out.close();
}
/**
* Writing a gif file as an overview for this GTopo30.
*
* @param gc
* The GridCoverage to write
* @param file
* The destination object (can be a File or ZipOutputStream)
* @param dest
*
* @throws IOException
* If the file could not be written
*/
private void writeGIF(final GridCoverage2D gc, final String name,
Object dest) throws IOException {
ImageOutputStream out = null;
if (dest instanceof File) {
// writing gif image
out = ImageIO.createImageOutputStream(new File((File) dest,
new StringBuffer(name).append(".GIF").toString()));
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(gc.getName().toString() + ".GIF");
outZ.putNextEntry(e);
out = ImageIO.createImageOutputStream(outZ);
}
// rescaling to a smaller resolution in order to save space on storage
final GridCoverage2D gc1 = rescaleCoverage(gc);
// get the non-geophysiscs view
final GridCoverage2D gc2 = gc1.geophysics(false);
// get the underlying image
final RenderedImage image = gc2.getRenderedImage();
// write it down as a gif
final ParameterBlockJAI pbj = new ParameterBlockJAI("ImageWrite");
pbj.addSource(image);
pbj.setParameter("Output", out);
pbj.setParameter("Format", "gif");
JAI.create("ImageWrite", pbj, new RenderingHints(JAI.KEY_TILE_CACHE,
null));
if (dest instanceof File) {
out.close();
} else {
out.flush();
((ZipOutputStream) dest).closeEntry();
}
// disposing the old unused coverages
gc1.dispose(false);
}
/**
* Purpose of this method is rescaling the original coverage in order to
* create an overview for the shaded relief gif image to be put inside the
* gtopo30 set of files.
*
* @param gc
* the original {@link GridCoverage2D}.
*
* @return the rescaled {@link GridCoverage2D}.
*/
private GridCoverage2D rescaleCoverage(GridCoverage2D gc) {
final RenderedImage image = gc.getRenderedImage();
final int width = image.getWidth();
final int height = image.getHeight();
if ((height < GIF_HEIGHT) && (width < GIF_WIDTH)) {
return gc;
}
// new grid range
final GeneralGridEnvelope newGridrange = new GeneralGridEnvelope(new int[] {0, 0 }, new int[] { GIF_WIDTH, GIF_HEIGHT });
final GridGeometry2D newGridGeometry = new GridGeometry2D(newGridrange,gc.getEnvelope());
// resample this coverage
final ParameterValueGroup pvg= resampleFactory.getParameters();
pvg.parameter("Source").setValue(gc);
pvg.parameter("GridGeometry").setValue(newGridGeometry);
pvg.parameter("InterpolationType").setValue(new InterpolationBilinear());
return (GridCoverage2D) resampleFactory.doOperation(pvg, hints);
}
/**
* Write a projection file (.PRJ) using wkt
*
* @param gc
* The GridCoverage to write
* @param file
* The destination object (can be a File or ZipOutputStream)
* @param dest
*
* @throws IOException
* If the file could not be written
*/
private void writePRJ(final GridCoverage2D gc, String name, Object dest)
throws IOException {
if (dest instanceof File) {
// create the file
final BufferedWriter fileWriter = new BufferedWriter(
new FileWriter(new File((File) dest, new StringBuffer(name)
.append(".PRJ").toString())));
// write information on crs
fileWriter.write(gc.getCoordinateReferenceSystem().toWKT());
fileWriter.close();
} else {
final ZipOutputStream out = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(new StringBuffer(gc.getName()
.toString()).append(".PRJ").toString());
out.putNextEntry(e);
out.write(gc.getCoordinateReferenceSystem().toWKT().getBytes());
out.closeEntry();
}
}
/**
* Writes the stats file (.STX).
*
* @param image
* The GridCoverage to write
* @param file
* The destination object (can be a File or ZipOutputStream)
* @param gc
* DOCUMENT ME!
*
* @throws IOException
* If the file could not be written
*/
private void writeStats(final PlanarImage image, String name, Object dest,
final GridCoverage2D gc) throws IOException {
ParameterBlock pb = new ParameterBlock();
// /////////////////////////////////////////////////////////////////////
//
// we need to evaluate stats first using jai
//
// /////////////////////////////////////////////////////////////////////
final int visibleBand = CoverageUtilities.getVisibleBand(image);
final GridSampleDimension sd = (GridSampleDimension) gc
.getSampleDimension(visibleBand);
final double[] Max = new double[] { sd.getMaximumValue() };
final double[] Min = new double[] { -9999.0 };
// histogram
pb.addSource(image);
pb.add(null); // no roi
pb.add(1);
pb.add(1);
pb.add(new int[] { (int) (Max[0] - Min[0] + 1) });
pb.add(Min);
pb.add(Max);
pb.add(1);
// /////////////////////////////////////////////////////////////////////
//
// Create the histogram
//
// /////////////////////////////////////////////////////////////////////
final PlanarImage histogramImage = JAI.create("histogram", pb,
new RenderingHints(JAI.KEY_TILE_CACHE, null));
final Histogram hist = (Histogram) histogramImage
.getProperty("histogram");
pb.removeParameters();
pb.removeSources();
// /////////////////////////////////////////////////////////////////////
//
// Write things
//
// /////////////////////////////////////////////////////////////////////
if (dest instanceof File) {
// files destinations
// write statistics
if (dest instanceof File) {
dest = new File((File) dest, new StringBuffer(name).append(
".STX").toString());
}
// writing world file
final PrintWriter p = new PrintWriter(new FileOutputStream(
((File) dest)));
p.print(1);
p.print(" ");
p.print((int) Min[0]);
p.print(" ");
p.print((int) Max[0]);
p.print(" ");
p.print(hist.getMean()[0]);
p.print(" ");
p.print(hist.getStandardDeviation()[0]);
p.close();
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(name + ".STX");
outZ.putNextEntry(e);
// writing world file
outZ.write("1".getBytes());
outZ.write(" ".getBytes());
outZ.write(new Integer((int) Min[0]).toString().getBytes());
outZ.write(" ".getBytes());
outZ.write(new Integer((int) Max[0]).toString().getBytes());
outZ.write(" ".getBytes());
outZ.write(new Double(hist.getMean()[0]).toString().getBytes());
outZ.write(" ".getBytes());
outZ.write(new Double(hist.getStandardDeviation()[0]).toString()
.getBytes());
((ZipOutputStream) dest).closeEntry();
}
histogramImage.dispose();
}
/**
* Writes the world file (.DMW)
*
* @param gc
* The GridCoverage to write
* @param worldFile
* The destination world file (can be a file or a
* ZipOutputStream)
*
* @throws IOException
* if the file could not be written
*/
private void writeWorldFile(final GridCoverage2D gc, String name,
Object dest) throws IOException {
// final RenderedImage image = (PlanarImage) gc.getRenderedImage();
/**
* It is worth to point out that here I build the values using the axes
* order specified in the CRs which can be either LAT,LON or LON,LAT.
* This is important since we ned to know how to assocaite the
* underlying raster dimensions with the envelope which is in CRS
* values.
*/
// /////////////////////////////////////////////////////////////////////
//
// trying to understand the direction of the first axis in order to
// understand how to associate the value to the crs.
//
// /////////////////////////////////////////////////////////////////////
final AffineTransform gridToWorld = (AffineTransform) gc
.getGridGeometry().getGridToCRS2D();
boolean lonFirst = (XAffineTransform.getSwapXY(gridToWorld) != -1);
// /////////////////////////////////////////////////////////////////////
//
// associate value to crs
//
// /////////////////////////////////////////////////////////////////////
final double xPixelSize = (lonFirst) ? gridToWorld.getScaleX()
: gridToWorld.getShearY();
final double rotation1 = (lonFirst) ? gridToWorld.getShearX()
: gridToWorld.getScaleY();
final double rotation2 = (lonFirst) ? gridToWorld.getShearY()
: gridToWorld.getScaleX();
final double yPixelSize = (lonFirst) ? gridToWorld.getScaleY()
: gridToWorld.getShearX();
final double xLoc = lonFirst ? gridToWorld.getTranslateX()
: gridToWorld.getTranslateY();
final double yLoc = lonFirst ? gridToWorld.getTranslateY()
: gridToWorld.getTranslateX();
if (dest instanceof File) {
dest = new File((File) dest, new StringBuffer(name).append(
".DMW").toString());
// writing world file
final PrintWriter out = new PrintWriter(new FileOutputStream(
(File) dest));
out.println(xPixelSize);
out.println(rotation1);
out.println(rotation2);
out.println(yPixelSize);
out.println(xLoc);
out.println(yLoc);
out.close();
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(gc.getName().toString()
+ ".DMW");
outZ.putNextEntry(e);
// writing world file
outZ.write(Double.toString(xPixelSize).getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(rotation1).toString().getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(rotation2).toString().getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(xPixelSize).toString().getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(yPixelSize).toString().getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(xLoc).toString().getBytes());
outZ.write("\n".getBytes());
outZ.write(Double.toString(yLoc).toString().getBytes());
outZ.write("\n".getBytes());
((ZipOutputStream) dest).closeEntry();
}
}
/**
* Writes the digital elevation model file (.DEM). The default byte order is
* BIG_ENDIAN.
*
* @param image
* The GridCoverage object to write
* @param name
* DOCUMENT ME!
* @param dest
* The destination object (can be a File or a ZipOutputStream)
*
* @throws FileNotFoundException
* If the destination file could not be found
* @throws IOException
* If the file could not be written
*/
private void writeDEM(PlanarImage image, final String name, Object dest)
throws FileNotFoundException, IOException {
ImageOutputStream out;
if (dest instanceof File) {
// write statistics
if (dest instanceof File) {
dest = new File((File) dest, new StringBuffer(name).append(
".DEM").toString());
}
out = ImageIO.createImageOutputStream((File) dest);
} else {
final ZipOutputStream outZ = (ZipOutputStream) dest;
final ZipEntry e = new ZipEntry(name + ".DEM");
outZ.putNextEntry(e);
out = ImageIO.createImageOutputStream(outZ);
}
out.setByteOrder(java.nio.ByteOrder.BIG_ENDIAN);
// untile the image in case it is tiled
// otherwise we could add tiles which are unexistant in the original
// data
// generating failures when reading back the data again.
image = untileImage(image);
// requesting an imageio writer
// setting tile parameters in order to tile the image on the disk
final ParameterBlockJAI pbjW = new ParameterBlockJAI("ImageWrite");
pbjW.addSource(image);
pbjW.setParameter("Format", "raw");
pbjW.setParameter("Output", out);
JAI.create("ImageWrite", pbjW);
if (dest instanceof File) {
out.flush();
out.close();
} else {
((ZipOutputStream) dest).closeEntry();
}
}
/**
* This method has the objective of untiling the final image to write on
* disk since we do not want to have tiles added to the file on disk causing
* failures when reading it back into memory.
*
* @param image
* Image to untile.
*
* @return Untiled image.
*/
private PlanarImage untileImage(RenderedImage image) {
final ParameterBlockJAI pbj = new ParameterBlockJAI("format");
pbj.addSource(image);
pbj.setParameter("dataType", image.getSampleModel().getTransferType());
final ImageLayout layout = new ImageLayout(image);
layout.unsetTileLayout();
layout.setTileGridXOffset(0);
layout.setTileGridYOffset(0);
layout.setTileHeight(image.getHeight());
layout.setTileWidth(image.getWidth());
layout.setValid(ImageLayout.TILE_GRID_X_OFFSET_MASK
| ImageLayout.TILE_GRID_Y_OFFSET_MASK
| ImageLayout.TILE_HEIGHT_MASK | ImageLayout.TILE_WIDTH_MASK);
final RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,
layout);
// avoid caching this image
return JAI.create("format", pbj, hints);
}
}