/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-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;
// J2SE dependencies
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderedImageFactory;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.media.jai.CRIFImpl;
import javax.media.jai.ComponentSampleModelJAI;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.OperationRegistry;
import javax.media.jai.PlanarImage;
import javax.media.jai.PointOpImage;
import javax.media.jai.iterator.RectIterFactory;
import javax.media.jai.iterator.WritableRectIter;
import javax.media.jai.registry.RenderedRegistryMode;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.AbstractGridCoverage;
import org.geotools.image.TransfertRectIter;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.image.ImageUtilities;
/**
* An image that contains transformed samples, specifically this method will transform the NoData value
* using a new supplied one. A new layout is used in order to convert to the required image layout. Default
* values for this operation can be used to set the NoData and the layout to the values needed for
* the GTOPO30 writer.
*
* Images are created using the
* {@code NoDataReplacerOpImage.NoDataReplacerCRIF} inner class, where "CRIF" stands for
* {@link java.awt.image.renderable.ContextualRenderedImageFactory}. The image
* operation name is "org.geotools.gce.NoDataReplacer".
*
*
* @source $URL$
* @version $Id$
* @author Simone Giannecchini
*
* @since 2.2
*/
public final class NoDataReplacerOpImage extends PointOpImage {
/**The operation name.*/
public static final String OPERATION_NAME = "org.geotools.gce.gtopo30.NoDataReplacer";
/**Constant that tell me the margin when checking for equality with floating point values*/
private double EPS;
/**Old no data value.*/
private Number oldNoData;
/**New no data value.*/
private int newNoData;
/**It tells me whether or not the old no data is NaN.*/
private boolean oldNoDataIsNaN;
/**
* Constructs a new {@code NoDataReplacerOpImage}.
*
* @param image The source image.
* @param oldNoData The old NoData value to be substituted.
* @param newNoData The new NoData value to be employed.
* @param EPS Margin for equality checks.
* @param hints Suplpied RenderingHints.
*/
private NoDataReplacerOpImage(final RenderedImage image,
final Number oldNoData,
final Short newNoData,
final Double EPS,
final RenderingHints hints)
{
super(image, NoDataReplacerOpImage.getRightLayout(image), hints, false);
this.EPS = EPS.doubleValue();
this.oldNoData=oldNoData;
this.oldNoDataIsNaN=Double.isNaN(oldNoData.doubleValue());
this.newNoData=newNoData.intValue();
permitInPlaceOperation();
}
/**
* @todo Creation of non banded sample models
* @param image Image to work on.
* @return New Image Layout.
*/
private static ImageLayout getRightLayout(RenderedImage image) {
final SampleModel sm=image.getSampleModel();
final int dataType=DataBuffer.TYPE_SHORT;
if(sm.getDataType()==dataType)
return new ImageLayout(image);
final ColorModel cm=image.getColorModel();
if(sm instanceof ComponentSampleModel)
{
final ColorModel newCm = new ComponentColorModel(
cm.getColorSpace(),
false,
false,
cm.getTransparency(),
dataType);
final SampleModel newSm = new ComponentSampleModelJAI(
dataType,
sm.getWidth(),
sm.getHeight(),
((ComponentSampleModel)sm).getPixelStride(),
((ComponentSampleModel)sm).getScanlineStride(),
((ComponentSampleModel)sm).getBankIndices(),
((ComponentSampleModel)sm).getBandOffsets()
);
final ImageLayout layout = ImageUtilities.getImageLayout(image);
layout.setColorModel(newCm);
layout.setSampleModel(newSm);
return layout;
}
else
;//do nothing for the moment
return null;
}
/**
* Computes one of the destination image tile.
*
* @todo There is two optimisations we could do here:
* <ul>
* <li>If source and destination are the same raster, then a single
* {@link WritableRectIter} object would be more efficient (the
* hard work is to detect if source and destination are the same).</li>
* <li>If the destination image is a single-banded, non-interleaved
* sample model, we could apply the transform directly in the
* {@link java.awt.image.DataBuffer}. We can even avoid to copy
* sample value if source and destination raster are the same.</li>
* </ul>
*
* @param sources An array of length 1 with source image.
* @param dest The destination tile.
* @param destRect the rectangle within the destination to be written.
*/
protected void computeRect(final PlanarImage[] sources,
final WritableRaster dest,
final Rectangle destRect)
{
final PlanarImage source = sources[0];
WritableRectIter iterator = RectIterFactory.createWritable(dest, destRect);
if (true) {
// TODO: Detect if source and destination rasters are the same. If they are
// the same, we should skip this block. Iteration will then be faster.
iterator = TransfertRectIter.create(RectIterFactory.create(source, destRect), iterator);
}
formatRect(iterator);
}
/**
* Transform a raster. Only the current band in {@code iterator} will be transformed.
* The transformed value are write back in the {@code iterator}. If a different
* destination raster is wanted, a {@link org.geotools.resources.image.DualRectIter}
* may be used.
*
* @param iterator An iterator to iterate among the samples to transform.
*/
private void formatRect(WritableRectIter iterator) {
double actualValue=0.0;
iterator.startLines();
if (!iterator.finishedLines())
do {
iterator.startPixels();
if (!iterator.finishedPixels())
do {
//get the actual value
actualValue = iterator.getSampleDouble();
//substituting a NaN
if(oldNoDataIsNaN)
if(Double.isNaN(actualValue))
iterator.setSample(newNoData);
else
if(Math.abs(oldNoData.doubleValue()-actualValue)<=EPS)
iterator.setSample(newNoData);
else
iterator.setSample(actualValue);
}
while (!iterator.nextPixelDone());
}
while (!iterator.nextLineDone());
}
/////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// REGISTRATION OF "NoDataReplacer" IMAGE OPERATION ////////
//////// ////////
/////////////////////////////////////////////////////////////////////////////////
/**
* The operation descriptor for the "NoDataReplacer" operation. This operation
* is used to change the format of an Image while replacing the NoData value with a new
* one as requested. The difference between this method and the usual format operation presents
* in JAI is the possibility to replace the NoData value directly when it is like Double.NaN or
* Float.NaN.
*
*/
private static final class NoDataReplacerDescriptor extends OperationDescriptorImpl {
/**
* Comment for <code>serialVersionUID</code>
*/
private static final long serialVersionUID = 1L;
/**
* Construct the descriptor.
*/
public NoDataReplacerDescriptor() {
super(new String[][]{{"GlobalName", OPERATION_NAME},
{"LocalName", OPERATION_NAME},
{"Vendor", "Geotools 2"},
{"Description", "Nodata replacement and layout adjustment."},
{"DocURL", "http://www.geotools.org/"},
{"Version", "1.0"}},
new String[] {RenderedRegistryMode.MODE_NAME}, 1,
new String[] {"oldNoData","newNoData","EPS"}, // Argument names
new Class [] {Number.class,Short.class,Double.class}, // Argument classes
new Object[] {new Double(Double.NaN),new Short((short)-9999),new Double(10.0E-6)}, // Default values for parameters,
null // No restriction on valid parameter values.
);
}
/**
* Returns {@code true} if the parameters are valids. This implementation check
* that the number of bands in the source image is equals to the number of supplied
* sample dimensions, and that all sample dimensions has categories.
*
* @param modeName The mode name (usually "Rendered").
* @param param The parameter block for the operation to performs.
* @param message A buffer for formatting an error message if any.
*/
protected boolean validateParameters(final String modeName,
final ParameterBlock param,
final StringBuffer message)
{
if (!super.validateParameters(modeName, param, message)) {
return false;
}
try{
// param.
// final RenderedImage source = (RenderedImage) param.getSource(0);
// final Number oldNoData= (Number) param.getObjectParameter(0);
// final Number newNoData= (Number) param.getObjectParameter(1);
// final Double EPS= (Double) param.getObjectParameter(1);
}
catch(Exception e){
message.append(e.getMessage());
return false;
}
return true;
}
}
/**
* The {@link RenderedImageFactory} for the "SampleTranscode" operation.
*/
private static final class NoDataReplacerCRIF extends CRIFImpl {
/**
* Creates a {@link RenderedImage} representing the results of an imaging
* operation for a given {@link ParameterBlock} and {@link RenderingHints}.
*/
public RenderedImage create(final ParameterBlock param,
final RenderingHints hints)
{
final RenderedImage source = (RenderedImage) param.getSource(0);
final Number oldNoData= (Number) param.getObjectParameter(0);
final Short newNoData= (Short) param.getObjectParameter(1);
final Double EPS= (Double) param.getObjectParameter(2);
return new NoDataReplacerOpImage(source, oldNoData,newNoData,EPS, hints);
}
}
/**
* Register the "SampleTranscode" image operation to the operation registry of
* the specified JAI instance. This method is invoked by the static initializer
* of {@link GridSampleDimension}.
*/
public static void register(final JAI jai) {
final OperationRegistry registry = jai.getOperationRegistry();
try {
registry.registerDescriptor(new NoDataReplacerDescriptor());
registry.registerFactory(RenderedRegistryMode.MODE_NAME, OPERATION_NAME,
"gce.geotools.org", new NoDataReplacerCRIF());
} catch (IllegalArgumentException exception) {
final LogRecord record = Loggings.format(Level.SEVERE,
LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1, OPERATION_NAME);
record.setSourceClassName("GridSampleDimension");
record.setSourceMethodName("<classinit>");
record.setThrown(exception);
AbstractGridCoverage.LOGGER.log(record);
}
}
}