/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2010, 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.grassraster.core;
import java.awt.image.RenderedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Date;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import org.geotools.gce.grassraster.DummyProgressListener;
import org.geotools.gce.grassraster.GrassBinaryImageReader;
import org.geotools.gce.grassraster.JGrassConstants;
import org.geotools.gce.grassraster.JGrassMapEnvironment;
import org.geotools.gce.grassraster.JGrassRegion;
import org.geotools.gce.grassraster.metadata.GrassBinaryImageMetadata;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.ProgressListener;
/**
* Grass binary data input/ouput handler.
* <p>
* Reading and writing is supported.
* </p>
*
* @author Andrea Antonello (www.hydrologis.com)
* @since 3.0
* @see GrassBinaryImageReader
* @see GrassBinaryImageReadParam
* @see GrassBinaryImageMetadata
*
* @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/build/maven/javadoc/../../../modules/unsupported/grassraster/src/main/java/org/geotools/gce/grassraster/core/GrassBinaryRasterWriteHandler.java $
*/
public class GrassBinaryRasterWriteHandler {
/**
* {@linkplain ImageOutputStream} used to write the data to file.
*/
private ImageOutputStream imageOS = null;
/**
* {@linkplain ImageOutputStream} used to write the data's null bitmap to file.
*/
private ImageOutputStream imageNullFileOS = null;
/**
* the value used to represent non existing data for in the raster.
*/
private Double noData = Double.NaN;
/**
* The {@linkplain JGrassMapEnvironment environment} needed for raster writing.
*/
private JGrassMapEnvironment writerGrassEnv = null;
/**
* the long array that keeps the addresses of the starting point (bytes in the file) of each
* raster row.
*/
private long[] addressesOfRows;
/**
* the current position of the pointer in the file.
*/
private long pointerInFilePosition = 0l;
/**
* the range of the raster map as an array of minimum value and maximum value.
*/
private final double[] range = new double[]{Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
/**
* the data type for the output map.
* <p>
* supported are 1 for float and 2 for double (jgrass only supports those two), the default is
* double.
* </p>
*/
private int outputToDiskType = 2;
private boolean jump = false;
private boolean abortRequired;
private JGrassRegion writeRegion = null;
private ProgressListener monitor = new DummyProgressListener();
/**
* A constructor to build a {@link GrassBinaryRasterWriteHandler} usable for writing grass
* rasters.
*
* @param destMapset the mapset file into which the map has to be written.
* @param newMapName the name for the written map.
* @param monitor
*/
public GrassBinaryRasterWriteHandler( File destMapset, String newMapName,
ProgressListener monitor ) {
if (monitor != null)
this.monitor = monitor;
writerGrassEnv = new JGrassMapEnvironment(destMapset, newMapName);
abortRequired = false;
}
/**
* Writes the raster, given an raster iterator and region metadata.
*
* @param renderedImage the {@link RenderedImage} to write.
* @param columns the columns of the raster to write.
* @param rows the rows of the raster to write.
* @param west the western bound of the raster to write.
* @param south the southern bound of the raster to write.
* @param xRes the east-west resolution of the raster to write.
* @param yRes the north-south resolution of the raster to write.
* @param noDataValue the value representing noData.
* @throws IOException
*/
public void writeRaster( RenderedImage renderedImage, int columns, int rows, double west,
double south, double xRes, double yRes, double noDataValue ) throws IOException {
boolean hasListeners = false;
if (!checkStructure())
throw new IOException("Inconsistent output structure for grass map. Check your paths.");
/*
* open the streams: the file for the map to create but also the needed null-file inside of
* the cell_misc folder
*/
imageOS = ImageIO.createImageOutputStream(writerGrassEnv.getFCELL());
imageNullFileOS = ImageIO.createImageOutputStream(writerGrassEnv.getCELLMISC_NULL());
double east = west + ((double) columns) * xRes;
double north = south + ((double) rows) * yRes;
JGrassRegion dataWindow = new JGrassRegion(west, east, south, north, rows, columns);
createEmptyHeader(rows);
if (hasListeners && abortRequired) {
return;
}
/*
* finally writing to disk
*/
CompressesRasterWriter crwriter = new CompressesRasterWriter(outputToDiskType, noDataValue,
jump, range, pointerInFilePosition, addressesOfRows, dataWindow, monitor,
writerGrassEnv.getMapName());
crwriter.compressAndWrite(imageOS, imageNullFileOS, renderedImage);
createUtilityFiles(dataWindow);
}
/**
* Calculates the region that is going to be written.
*
* @return the region that will be written by this Writer.
* @throws IOException
*/
public JGrassRegion getWriteRegion() throws IOException {
if (writeRegion == null) {
File wind = writerGrassEnv.getWIND();
writeRegion = new JGrassRegion(wind.getAbsolutePath());
}
return writeRegion;
}
public void setWriteRegion( JGrassRegion writeRegion ) {
this.writeRegion = writeRegion;
}
/**
* Closes the I/O streams.
*/
public void close() throws IOException {
if (imageOS != null) {
imageOS.close();
imageNullFileOS.close();
}
}
/**
* checks if the needed GRASS structure folders are available.
* <p>
* <b>NOTE:</b> they could be missing if the mapset has just been created and this is the first
* file that gets into it.
* </p>
* <p>
* <b>INFO:</b> this is a writer method.
* </p>
*
* @return true is the structure is ok.
*/
private boolean checkStructure() {
File ds;
File mapset = writerGrassEnv.getMAPSET();
String name = writerGrassEnv.getCELL().getName();
String mapsetPath = mapset.getAbsolutePath();
ds = new File(mapsetPath + File.separator + JGrassConstants.CATS + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.CELL + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.CELL_MISC + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.CELL_MISC + File.separator
+ name);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.FCELL + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.CELLHD + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.COLR + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
ds = new File(mapsetPath + File.separator + JGrassConstants.HIST + File.separator);
if (!ds.exists())
if (!ds.mkdir())
return false;
return true;
}
/**
* Creates the binary header for the raster file.
* <p>
* The space for the header of the rasterfile is created, filling the spaces with zeros. After
* the compression the values will be rewritten
* </p>
*
* @param rows number of rows that will be written.
* @throws IOException if an error occurs while trying to write the header.
*/
private void createEmptyHeader( int rows ) throws IOException {
addressesOfRows = new long[rows + 1];
// the size of a long in C?
imageOS.write(4);
// write the addresses of the row begins. Since we don't know how
// much
// they will be compressed, they will be filled after the
// compression
for( int i = 0; i < rows + 1; i++ ) {
imageOS.writeInt(0);
}
pointerInFilePosition = imageOS.getStreamPosition();
addressesOfRows[0] = pointerInFilePosition;
}
/**
* Creates all support files needed in the grass filesystem for a raster map.
*
* @param dataRegion data region to be written, used for writing of the cellheader.
*/
private void createUtilityFiles( JGrassRegion dataRegion ) throws IOException {
// create the right files in the right places
// cats/<name>
OutputStreamWriter catsWriter = new OutputStreamWriter(new FileOutputStream(writerGrassEnv
.getCATS()));
catsWriter.write("# xyz categories\n#\n#\n 0.00 0.00 0.00 0.00"); //$NON-NLS-1$
catsWriter.close();
// cell/<name>
OutputStreamWriter cellWriter = new OutputStreamWriter(new FileOutputStream(writerGrassEnv
.getCELL()));
cellWriter.write(""); //$NON-NLS-1$
cellWriter.close();
// cell_misc/<name>/<files>
// the directory <name> in cell_misc has already been created in
// writeMapInActiveRegion (or extended) of the Class Mapset (or
// extended)
// f_format
OutputStreamWriter cell_miscFormatWriter = new OutputStreamWriter(new FileOutputStream(
writerGrassEnv.getCELLMISC_FORMAT()));
if (outputToDiskType * 4 == 8) {
cell_miscFormatWriter.write("type: double\nbyte_order: xdr\nlzw_compression_bits: -1"); //$NON-NLS-1$
} else {
cell_miscFormatWriter.write("type: float\nbyte_order: xdr\nlzw_compression_bits: -1"); //$NON-NLS-1$
}
cell_miscFormatWriter.close();
// f_quant
OutputStreamWriter cell_miscQantWriter = new OutputStreamWriter(new FileOutputStream(
writerGrassEnv.getCELLMISC_QUANT()));
cell_miscQantWriter.write("round"); //$NON-NLS-1$
cell_miscQantWriter.close();
// f_range
OutputStream cell_miscRangeStream = new FileOutputStream(writerGrassEnv.getCELLMISC_RANGE());
cell_miscRangeStream.write(double2bytearray(range[0]));
cell_miscRangeStream.write(double2bytearray(range[1]));
cell_miscRangeStream.close();
/*
* need to reread the wind file to get the proj and zone (GRASS will not work if the cellhd
* is not equal to the WIND proj)
*/
JGrassRegion tmp = getWriteRegion();
createCellhd(tmp.getProj(), tmp.getZone(), dataRegion.getNorth(), dataRegion.getSouth(),
dataRegion.getEast(), dataRegion.getWest(), dataRegion.getCols(), dataRegion
.getRows(), dataRegion.getNSResolution(), dataRegion.getWEResolution(), -1,
1);
// hist/<name>
OutputStreamWriter windFile = new OutputStreamWriter(new FileOutputStream(writerGrassEnv
.getHIST()));
Date date = new Date();
windFile.write(date + "\n"); //$NON-NLS-1$
windFile.write(writerGrassEnv.getCELL().getName() + "\n"); //$NON-NLS-1$
windFile.write(writerGrassEnv.getMAPSET().getAbsolutePath() + "\n"); //$NON-NLS-1$
windFile.write(System.getProperty("user.name") + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
windFile.write("DCELL\n"); //$NON-NLS-1$
windFile.write("\n\nCreated by imageio-ext enabled JGrass\n"); //$NON-NLS-1$
windFile.close();
// now all the files have been created
}
/**
* Changes the cellhd file inserting the new values passed.
*
* @param chproj the proj value.
* @param chzone the zone value.
* @param chn northern boundary.
* @param chs southern boundary.
* @param che eastern boundary.
* @param chw western boundary.
* @param chcols number of columns.
* @param chrows number of rows.
* @param chnsres the north-south resolution.
* @param chewres the east-west resolution.
* @param chformat the map type.
* @param chcompressed the compression type.
* @throws IOException
*/
@SuppressWarnings("nls")
private void createCellhd( int chproj, int chzone, double chn, double chs, double che,
double chw, int chcols, int chrows, double chnsres, double chewres, int chformat,
int chcompressed ) throws IOException {
StringBuffer data = new StringBuffer(512);
data.append("proj: " + chproj + "\n").append("zone: " + chzone + "\n").append(
"north: " + chn + "\n").append("south: " + chs + "\n").append(
"east: " + che + "\n").append("west: " + chw + "\n").append(
"cols: " + chcols + "\n").append("rows: " + chrows + "\n").append(
"n-s resol: " + chnsres + "\n").append("e-w resol: " + chewres + "\n").append(
"format: " + chformat + "\n").append("compressed: " + chcompressed);
OutputStreamWriter windFile = new OutputStreamWriter(new FileOutputStream(writerGrassEnv
.getCELLHD()));
windFile.write(data.toString());
windFile.close();
}
/**
* Converts a double value to its byte representation.
*
* @param value the value to convert.
* @return the byte array for the double.
*/
private byte[] double2bytearray( double value ) {
long l = Double.doubleToLongBits(value);
byte[] b = new byte[8];
int shift = 64 - 8;
for( int k = 0; k < 8; k++, shift -= 8 ) {
b[k] = (byte) (l >>> shift);
}
return b;
}
/**
* Setter for the noData value.
*
* @param noData the nodata value to set.
*/
public void setNoData( double noData ) {
this.noData = noData;
}
/**
* Getter for the noData value.
*
* @return the nodata value.
*/
public double getNoData() {
return noData;
}
/**
* Reads the crs definition for the map.
* <p>
* The definition for grass maps is held in the location. Grass projection definitions are
* usually in a non parsable internal format. In JGrass we ask the user to choose the CRS. If
* the user doesn't do so, the CRS will result to be undefined.
* </p>
*
* @return the {@link CoordinateReferenceSystem} for the map. Null if it is not defined.
* @throws IOException if there were problems in parsing the CRS file.
*/
public CoordinateReferenceSystem getCrs() throws IOException {
String locationPath = writerGrassEnv.getLOCATION().getAbsolutePath();
CoordinateReferenceSystem readCrs = null;
String projWtkFilePath;
projWtkFilePath = locationPath + File.separator + JGrassConstants.PERMANENT_MAPSET
+ File.separator + JGrassConstants.PROJ_WKT;
File projWtkFile = new File(projWtkFilePath);
if (projWtkFile.exists()) {
BufferedReader crsReader = new BufferedReader(new FileReader(projWtkFile));
StringBuffer wtkString = new StringBuffer();
try {
String line = null;
while( (line = crsReader.readLine()) != null ) {
wtkString.append(line.trim());
}
} finally {
crsReader.close();
}
try {
readCrs = CRS.parseWKT(wtkString.toString());
} catch (FactoryException e) {
throw new IOException(e.getLocalizedMessage());
}
return readCrs;
} else {
return null;
}
}
/**
* sets the abortrequired flag to true.
* <p>
* As soon as possible that should abort the reader.
* </p>
*/
public void abort() {
abortRequired = true;
}
/**
* Getter for the abortrequired flag.
*
* @return the abortRequired flag.
*/
public boolean isAborting() {
return abortRequired;
}
}