/*
* 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.test.stress;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.awt.image.RenderedImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.Locale;
import org.opengis.geometry.Envelope;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.TransformException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.geometry.Envelopes;
import org.geotoolkit.image.io.XImageIO;
import org.geotoolkit.image.jai.Registry;
import org.geotoolkit.coverage.grid.ViewType;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.grid.GeneralGridEnvelope;
import org.geotoolkit.coverage.grid.GeneralGridGeometry;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.coverage.io.GridCoverageReadParam;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.coverage.io.GridCoverageWriteParam;
import org.geotoolkit.coverage.io.GridCoverageWriter;
import org.geotoolkit.coverage.io.ImageCoverageReader;
import org.geotoolkit.coverage.io.ImageCoverageWriter;
import org.geotoolkit.coverage.processing.Operations;
import org.geotoolkit.image.io.mosaic.TileManager;
import org.geotoolkit.image.io.mosaic.TileManagerFactory;
/**
* A stressor for the {@code geotk-coverage-io} module.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.15
*
* @since 3.15
*/
public class CoverageReadWriteStressor extends Stressor {
/**
* The coverage reader to stress.
*/
protected final GridCoverageReader reader;
/**
* A grid coverage writer for testing write operations, or {@code null} if this
* {@code CoverageReadWriteStressor} instance is testing only read operations.
* <p>
* Will be created when first needed.
*/
private transient GridCoverageWriter writer;
/**
* The index of the image to read (usually 0).
*/
protected final int imageIndex;
/**
* If specified, reproject the request result in the given CRS.
*/
protected CoordinateReferenceSystem outputCRS;
/**
* If specified, write the request result in an image of the given format and read it back. It
* must be a format name recognized by Image I/O. The {@code "(native)"} or {@code "(standard)"}
* part, if any, shall be been processed outside this class.
*
* @see #processFormatName(String)
*/
protected String outputFormat;
/**
* A buffer where to write to the image, or {@code null} if none.
* Will be created when first needed.
*/
private transient MemoryOutputStream out;
/**
* An image reader, used only for checking the image created by {@link #writer}.
* Will be created when first needed.
*/
private transient ImageReader imageReader;
/**
* Creates a new stressor for the given input. This constructor creates automatically
* an {@link ImageCoverageReader} for the given input.
*
* @param input The input to use.
* @throws CoverageStoreException If an error occurred while reading the input.
*/
public CoverageReadWriteStressor(final Object input) throws CoverageStoreException {
this(createReader(input), 0);
}
/**
* Creates a new stressor for the given reader. Callers shall
* {@linkplain GridCoverageReader#setInput set the reader input}
* before to invoke this method.
*
* @param reader The coverage reader to stress.
* @param imageIndex The index of the image to read (usually 0).
* @throws CoverageStoreException If an error occurred while reading the input.
*/
private CoverageReadWriteStressor(final GridCoverageReader reader, final int imageIndex)
throws CoverageStoreException
{
super(clip(reader.getGridGeometry(imageIndex)));
this.reader = reader;
this.imageIndex = imageIndex;
}
/**
* Sets the logging level to the given value.
*/
@Override
public void setLogLevel(final Level level) {
if (reader != null) reader.setLogLevel(level);
if (writer != null) writer.setLogLevel(level);
super.setLogLevel(level);
}
/**
* Clips the given geometry. This can be used when the input raster include the poles
* and the output raster user some CRS like Mercator.
* <p>
* This code is disabled for now - it have to be enabled manually if desired.
* A future version may detect automatically whatever a clip is desired or not
* depending on the source coordinate system axes.
*/
private static GeneralGridGeometry clip(GeneralGridGeometry geometry) {
if (false) {
GridEnvelope range = geometry.getExtent();
final int[] lower = range.getLow().getCoordinateValues();
final int[] upper = range.getHigh().getCoordinateValues();
for (int i=range.getDimension(); --i>=0;) {
final int hs = (upper[i] - lower[i] + 1) / 200;
lower[i] += hs;
upper[i] -= hs;
}
range = new GeneralGridEnvelope(lower, upper, true);
geometry = new GeneralGridGeometry(range, PixelInCell.CELL_CORNER,
geometry.getGridToCRS(PixelInCell.CELL_CORNER),
geometry.getCoordinateReferenceSystem());
}
return geometry;
}
/**
* Creates a reader for the given input. If the given object input is a file ending
* with {@code ".serialized"}, it will be deserialized.
*
* @param input The input to give to the image reader.
* @return The image reader using the given input.
*/
private static GridCoverageReader createReader(final Object input)
throws CoverageStoreException
{
if (input instanceof GridCoverageReader) {
return (GridCoverageReader) input;
}
final GridCoverageReader reader = new ImageCoverageReader();
reader.setInput(input);
return reader;
}
/**
* Creates the input of a coverage reader.
*
* @param input The input to give to the image reader.
* @return A potentially modified input to give to the image reader.
*/
static Object createReaderInput(Object input) throws CoverageStoreException {
if (input instanceof File || input instanceof Path) {
Path file = (input instanceof File) ? ((File) input).toPath() : (Path) input;
LOGGER.log(Level.INFO, "Loading {0}", file);
try {
input = TileManagerFactory.DEFAULT.create(file);
if (!Files.isRegularFile(file) || !file.getFileName().toString().endsWith(".serialized")) {
file = file.resolve(TileManager.SERIALIZED_FILENAME);
LOGGER.log(Level.INFO, "Saving {0}", file);
try (ObjectOutputStream bs = new ObjectOutputStream(Files.newOutputStream(file))) {
bs.writeObject(input);
}
}
} catch (IOException e) {
throw new CoverageStoreException(e);
}
}
return input;
}
/**
* If the given format name contains a variant (a {@code "(standard)"} or {@code "(native)"}
* suffix), configures the {@code IIORegistry} accordingly and returns the format name without
* the variant suffix.
*
* @param formatName The format name, with an optional variant suffix.
* @return The format name without variant suffix.
* @throws IllegalArgumentException If the variant suffix is not recognized.
*
* @see #outputFormat
*/
protected static String processFormatName(String formatName) throws IllegalArgumentException {
final int s = formatName.indexOf('(');
if (s >= 0) {
final String variant = formatName.substring(s).toLowerCase();
formatName = formatName.substring(0, s);
final boolean useNative;
switch (variant) {
case "(native)": useNative = true; break;
case "(standard)": useNative = false; break;
default: throw new IllegalArgumentException("Unrecognized format variant: " + variant);
}
Registry.setNativeCodecAllowed(formatName, ImageReaderSpi.class, useNative);
Registry.setNativeCodecAllowed(formatName, ImageWriterSpi.class, useNative);
}
return formatName;
}
/**
* Sets the locale of the reader and writer.
*
* @param locale The locale.
*/
public void setLocale(final Locale locale) {
reader.setLocale(locale);
if (writer != null) {
writer.setLocale(locale);
}
}
/**
* Reads the given random request. If {@link #outputFormat} is non-null, the image will be
* written in a memory buffer, then read again (unless the output is not shown in a viewer).
*
* @throws CoverageStoreException If an error occurred while using the {@link org.geotoolkit.coverage.io} API.
* @throws IOException If an error occurred while using the {@link javax.imageio} API.
* @throws TransformException If an error occurred while projecting the source envelope.
*/
@Override
protected RenderedImage executeQuery(final GeneralGridGeometry request)
throws CoverageStoreException, IOException, TransformException
{
/*
* Tests read operation.
*/
final GridCoverageReadParam readParam = new GridCoverageReadParam();
readParam.setEnvelope(request.getEnvelope());
readParam.setResolution(getResolution(request));
GridCoverage2D coverage = (GridCoverage2D) reader.read(imageIndex, readParam);
if (outputFormat != null) {
/*
* Tests write operation.
*/
final GridCoverageWriteParam writeParam = new GridCoverageWriteParam(readParam);
if (outputCRS != null) {
final Envelope sourceEnvelope = writeParam.getEnvelope();
final Envelope targetEnvelope = Envelopes.transform(sourceEnvelope, outputCRS);
final double[] resolution = writeParam.getResolution();
if (resolution != null) {
for (int i=0; i<resolution.length; i++) {
// The naive algoritm below assumes that axis order didn't changed.
resolution[i] *= targetEnvelope.getSpan(i) / sourceEnvelope.getSpan(i);
}
}
writeParam.setEnvelope(targetEnvelope);
writeParam.setResolution(resolution);
}
writeParam.setFormatName(outputFormat);
if (out == null) {
out = new MemoryOutputStream();
}
out.reset();
if (writer == null) {
writer = new ImageCoverageWriter();
writer.setLogLevel(getLogLevel());
}
writer.setOutput(out);
writer.write(coverage, writeParam);
writer.setOutput(null);
/*
* Reads the image that we just wrote, for checking purpose.
* We will skip this step if there is no visual check.
*/
if (viewer != null) {
final RenderedImage image;
try (ImageInputStream in = ImageIO.createImageInputStream(out.getInputStream())) {
if (imageReader == null) {
imageReader = XImageIO.getReaderByFormatName(outputFormat, null, Boolean.TRUE, Boolean.TRUE);
}
imageReader.setInput(in);
image = imageReader.read(0);
imageReader.reset();
}
return image;
}
} else if (outputCRS != null) {
/*
* No write operation, but a CRS is specified. Test reprojection.
*/
coverage = (GridCoverage2D) Operations.DEFAULT.resample(coverage, outputCRS);
}
return coverage.view(ViewType.RENDERED).getRenderedImage();
}
/**
* Disposes the reader, the writer and the buffer after the test is done.
*/
@Override
protected void dispose() throws Exception {
reader.dispose();
if (writer != null) {
writer.dispose();
writer = null;
}
if (imageReader != null) {
imageReader.dispose();
imageReader = null;
}
out = null;
super.dispose();
}
}