/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2010-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2010-2012, Geomatys
*
* 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.geotoolkit.coverage.io;
import java.awt.Rectangle;
import java.util.Iterator;
import java.util.concurrent.CancellationException;
import javax.imageio.ImageWriter;
import org.opengis.geometry.Envelope;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.geotoolkit.resources.Errors;
import org.apache.sis.geometry.Envelopes;
import org.geotoolkit.coverage.grid.GeneralGridEnvelope;
import static org.geotoolkit.image.io.MultidimensionalImageStore.*;
/**
* Base class of {@link GridCoverage} writers. Writing is a two steps process:
* <p>
* <ul>
* <li>The output must be set first using the {@link #setOutput(Object)} method.</li>
* <li>The actual writing is performed by a call to the
* {@link #write(GridCoverage, GridCoverageWriteParam)} method.</li>
* </ul>
* <p>
* Example:
*
* {@preformat java
* GridCoverage coverage = ...
* GridCoverageWriter writer = ...
* writer.setOutput(new File("MyCoverage.asc"));
* writer.write(coverage, null);
* }
*
* {@note This class is conceptually equivalent to the <code>ImageWriter</code> class provided
* in the standard Java library. Implementations of this class are often wrappers around a Java
* <code>ImageWriter</code>, converting geodetic coordinates to pixel coordinates before to
* delegate the writing of pixel values.}
*
* @author Martin Desruisseaux (Geomatys)
* @author Johann Sorel (Geomatys)
* @version 3.20
*
* @see ImageWriter
*
* @since 3.14
* @module
*/
public abstract class GridCoverageWriter extends GridCoverageStore implements CoverageWriter<GridCoverage>{
/**
* The output (typically a {@link java.io.File}, {@link java.net.URL} or {@link String}),
* or {@code null} if output is not set.
*/
Object output;
/**
* The bounds of the image requested by the user. This field is computed indirectly
* by the {@link #geodeticToPixelCoordinates geodeticToPixelCoordinates} method.
*
* @since 3.14
*/
transient Rectangle requestedBounds;
/**
* Creates a new instance.
*/
protected GridCoverageWriter() {
}
/**
* Sets the output destination to the given object. The output is typically a
* {@link java.io.File} or a {@link String} object. But some other types
* (e.g. {@link javax.imageio.stream.ImageOutputStream}) may be accepted
* as well depending on the implementation.
*
* {@section How streams are closed}
* <ul>
* <li>If the given output is an {@linkplain java.io.OutputStream output stream},
* {@linkplain javax.imageio.stream.ImageOutputStream image output stream} or
* a {@linkplain java.io.Writer writer}, then it is caller responsibility to
* close the given stream after usage.</li>
* <li>If an output stream has been generated automatically by this {@code GridCoverageWriter}
* from the given output object, then this coverage writer will close the stream when the
* {@link #reset()} or {@link #dispose()} method is invoked, or when a new output is set.</li>
* </ul>
*
* @param output The output (typically {@link java.io.File} or {@link String}) to be written.
* @throws IllegalArgumentException If the output is not a valid instance for this writer.
* @throws CoverageStoreException If the operation failed.
*
* @see ImageWriter#setOutput(Object)
*/
public void setOutput(Object output) throws CoverageStoreException {
this.output = output;
abortRequested = false;
}
/**
* Returns the output which was set by the last call to {@link #setOutput(Object)},
* or {@code null} if none.
*
* @return The current output, or {@code null} if none.
* @throws CoverageStoreException If the operation failed.
*
* @see ImageWriter#getOutput()
*/
public Object getOutput() throws CoverageStoreException {
return output;
}
/**
* Writes a single grid coverage.
*
* @param coverage The coverage to write.
* @param param Optional parameters used to control the writing process, or {@code null}.
* @throws IllegalStateException If the output destination has not been set.
* @throws CoverageStoreException If an error occurs while writing the information to the output destination.
* @throws CancellationException If {@link #abort()} has been invoked in an other thread during
* the execution of this method.
*/
@Override
public abstract void write(GridCoverage coverage, GridCoverageWriteParam param)
throws CoverageStoreException, CancellationException;
/**
* Writes one or many grid coverages. The default implementation delegates to
* {@link #write(GridCoverage, GridCoverageWriteParam)} if the given iterable
* contains exactly one coverage, or throws an {@link CoverageStoreException} otherwise.
*
* @param coverages The coverages to write.
* @param param Optional parameters used to control the writing process, or {@code null}.
* @throws IllegalStateException If the output destination has not been set.
* @throws CoverageStoreException If the iterable contains an unsupported number of coverages,
* or if an error occurs while writing the information to the output destination.
* @throws CancellationException If {@link #abort()} has been invoked in an other thread during
* the execution of this method.
*
* @since 3.20
*/
@Override
public void write(final Iterable<? extends GridCoverage> coverages, final GridCoverageWriteParam param)
throws CoverageStoreException, CancellationException
{
short errorKey = Errors.Keys.NoSuchElement_1;
final Iterator<? extends GridCoverage> it = coverages.iterator();
if (it.hasNext()) {
final GridCoverage coverage = it.next();
if (!it.hasNext()) {
write(coverage, param);
return;
}
errorKey = Errors.Keys.UnsupportedMultiOccurrence_1;
}
throw new CoverageStoreException(Errors.format(errorKey, GridCoverage.class));
}
/**
* A callback invoked by {@link #geodeticToPixelCoordinates geodeticToPixelCoordinates}
* in order to compute the {@link #requestedBounds} value. This value is of interest to
* the writer only (not to {@link ImageCoverageReader}), because the reader is allowed
* to returns a different coverage than the requested one, while the writer have to write
* the image as requested.
*/
@Override
final void computeRequestedBounds(final MathTransform destToExtractedGrid,
final Envelope requestEnvelope, final CoordinateReferenceSystem requestCRS)
throws TransformException, CoverageStoreException
{
final GridEnvelope gridEnvelope = new GeneralGridEnvelope(
Envelopes.transform(destToExtractedGrid.inverse(), requestEnvelope),
PixelInCell.CELL_CORNER, false);
for (int i=gridEnvelope.getDimension(); --i>=0;) {
if (gridEnvelope.getSpan(i) <= 0) {
String message = formatErrorMessage(Errors.Keys.ValueTendTowardInfinity);
if (requestCRS != null) {
message = requestCRS.getCoordinateSystem().getAxis(i).getName().getCode() + ": " + message;
}
throw new CoverageStoreException(message);
}
}
requestedBounds = new Rectangle(
gridEnvelope.getLow (X_DIMENSION),
gridEnvelope.getLow (Y_DIMENSION),
gridEnvelope.getSpan(X_DIMENSION),
gridEnvelope.getSpan(Y_DIMENSION));
}
/**
* Restores the {@code GridCoverageWriter} to its initial state.
*
* @throws CoverageStoreException If an error occurs while restoring to the initial state.
*
* @see ImageWriter#reset()
*/
@Override
public void reset() throws CoverageStoreException {
requestedBounds = null;
output = null;
super.reset();
}
/**
* Allows any resources held by this writer to be released. The result of calling
* any other method subsequent to a call to this method is undefined.
*
* @throws CoverageStoreException If an error occurs while disposing resources.
*
* @see ImageWriter#dispose()
*/
@Override
public void dispose() throws CoverageStoreException {
requestedBounds = null;
output = null;
super.dispose();
}
}