001: /*
002: * Geotools 2 - OpenSource mapping toolkit
003: * (C) 2003, Geotools Project Management Committee (PMC)
004: * (C) 2001, Institut de Recherche pour le Développement
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: *
021: * This package contains documentation from OpenGIS specifications.
022: * OpenGIS consortium's work is fully acknowledged here.
023: */
024: package org.geotools.gce.gtopo30;
025:
026: // J2SE dependencies
027: import java.awt.Rectangle;
028: import java.awt.RenderingHints;
029: import java.awt.image.ColorModel;
030: import java.awt.image.ComponentColorModel;
031: import java.awt.image.ComponentSampleModel;
032: import java.awt.image.DataBuffer;
033: import java.awt.image.RenderedImage;
034: import java.awt.image.SampleModel;
035: import java.awt.image.WritableRaster;
036: import java.awt.image.renderable.ParameterBlock;
037: import java.awt.image.renderable.RenderedImageFactory;
038: import java.util.logging.Level;
039: import java.util.logging.LogRecord;
040:
041: import javax.media.jai.CRIFImpl;
042: import javax.media.jai.ComponentSampleModelJAI;
043: import javax.media.jai.ImageLayout;
044: import javax.media.jai.JAI;
045: import javax.media.jai.OperationDescriptorImpl;
046: import javax.media.jai.OperationRegistry;
047: import javax.media.jai.PlanarImage;
048: import javax.media.jai.PointOpImage;
049: import javax.media.jai.iterator.RectIterFactory;
050: import javax.media.jai.iterator.WritableRectIter;
051: import javax.media.jai.registry.RenderedRegistryMode;
052:
053: import org.geotools.coverage.GridSampleDimension;
054: import org.geotools.coverage.grid.AbstractGridCoverage;
055: import org.geotools.image.TransfertRectIter;
056: import org.geotools.resources.i18n.Logging;
057: import org.geotools.resources.i18n.LoggingKeys;
058: import org.geotools.resources.image.ImageUtilities;
059:
060: /**
061: * An image that contains transformed samples, specifically this method will transform the NoData value
062: * using a new supplied one. A new layout is used in order to convert to the required image layout. Default
063: * values for this operation can be used to set the NoData and the layout to the values needed for
064: * the GTOPO30 writer.
065: *
066: * Images are created using the
067: * {@code NoDataReplacerOpImage.NoDataReplacerCRIF} inner class, where "CRIF" stands for
068: * {@link java.awt.image.renderable.ContextualRenderedImageFactory}. The image
069: * operation name is "org.geotools.gce.NoDataReplacer".
070: *
071: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/gtopo30/src/main/java/org/geotools/gce/gtopo30/NoDataReplacerOpImage.java $
072: * @version $Id: NoDataReplacerOpImage.java 22817 2006-11-17 17:24:55Z desruisseaux $
073: * @author Simone Giannecchini
074: *
075: * @since 2.2
076: */
077: public final class NoDataReplacerOpImage extends PointOpImage {
078: /**The operation name.*/
079: public static final String OPERATION_NAME = "org.geotools.gce.gtopo30.NoDataReplacer";
080:
081: /**Constant that tell me the margin when checking for equality with floating point values*/
082: private double EPS;
083:
084: /**Old no data value.*/
085: private Number oldNoData;
086:
087: /**New no data value.*/
088: private int newNoData;
089:
090: /**It tells me whether or not the old no data is NaN.*/
091: private boolean oldNoDataIsNaN;
092:
093: /**
094: * Constructs a new {@code NoDataReplacerOpImage}.
095: *
096: * @param image The source image.
097: * @param oldNoData The old NoData value to be substituted.
098: * @param newNoData The new NoData value to be employed.
099: * @param EPS Margin for equality checks.
100: * @param hints Suplpied RenderingHints.
101: */
102: private NoDataReplacerOpImage(final RenderedImage image,
103: final Number oldNoData, final Short newNoData,
104: final Double EPS, final RenderingHints hints) {
105: super (image, NoDataReplacerOpImage.getRightLayout(image),
106: hints, false);
107: this .EPS = EPS.doubleValue();
108: this .oldNoData = oldNoData;
109: this .oldNoDataIsNaN = Double.isNaN(oldNoData.doubleValue());
110: this .newNoData = newNoData.intValue();
111:
112: permitInPlaceOperation();
113: }
114:
115: /**
116: * @todo Creation of non banded sample models
117: * @param image Image to work on.
118: * @return New Image Layout.
119: */
120: private static ImageLayout getRightLayout(RenderedImage image) {
121:
122: final SampleModel sm = image.getSampleModel();
123: final int dataType = DataBuffer.TYPE_SHORT;
124: if (sm.getDataType() == dataType)
125: return new ImageLayout(image);
126: final ColorModel cm = image.getColorModel();
127: if (sm instanceof ComponentSampleModel) {
128: final ColorModel newCm = new ComponentColorModel(cm
129: .getColorSpace(), false, false, cm
130: .getTransparency(), dataType);
131: final SampleModel newSm = new ComponentSampleModelJAI(
132: dataType, sm.getWidth(), sm.getHeight(),
133: ((ComponentSampleModel) sm).getPixelStride(),
134: ((ComponentSampleModel) sm).getScanlineStride(),
135: ((ComponentSampleModel) sm).getBankIndices(),
136: ((ComponentSampleModel) sm).getBandOffsets());
137:
138: final ImageLayout layout = ImageUtilities
139: .getImageLayout(image);
140: layout.setColorModel(newCm);
141: layout.setSampleModel(newSm);
142: return layout;
143: } else
144: ;//do nothing for the moment
145:
146: return null;
147: }
148:
149: /**
150: * Computes one of the destination image tile.
151: *
152: * @todo There is two optimisations we could do here:
153: * <ul>
154: * <li>If source and destination are the same raster, then a single
155: * {@link WritableRectIter} object would be more efficient (the
156: * hard work is to detect if source and destination are the same).</li>
157: * <li>If the destination image is a single-banded, non-interleaved
158: * sample model, we could apply the transform directly in the
159: * {@link java.awt.image.DataBuffer}. We can even avoid to copy
160: * sample value if source and destination raster are the same.</li>
161: * </ul>
162: *
163: * @param sources An array of length 1 with source image.
164: * @param dest The destination tile.
165: * @param destRect the rectangle within the destination to be written.
166: */
167: protected void computeRect(final PlanarImage[] sources,
168: final WritableRaster dest, final Rectangle destRect) {
169: final PlanarImage source = sources[0];
170: WritableRectIter iterator = RectIterFactory.createWritable(
171: dest, destRect);
172: if (true) {
173: // TODO: Detect if source and destination rasters are the same. If they are
174: // the same, we should skip this block. Iteration will then be faster.
175: iterator = TransfertRectIter.create(RectIterFactory.create(
176: source, destRect), iterator);
177: }
178: formatRect(iterator);
179: }
180:
181: /**
182: * Transform a raster. Only the current band in {@code iterator} will be transformed.
183: * The transformed value are write back in the {@code iterator}. If a different
184: * destination raster is wanted, a {@link org.geotools.resources.image.DualRectIter}
185: * may be used.
186: *
187: * @param iterator An iterator to iterate among the samples to transform.
188: */
189: private void formatRect(WritableRectIter iterator) {
190:
191: double actualValue = 0.0;
192: iterator.startLines();
193: if (!iterator.finishedLines())
194: do {
195: iterator.startPixels();
196: if (!iterator.finishedPixels())
197: do {
198:
199: //get the actual value
200: actualValue = iterator.getSampleDouble();
201:
202: //substituting a NaN
203: if (oldNoDataIsNaN)
204: if (Double.isNaN(actualValue))
205: iterator.setSample(newNoData);
206: else if (Math.abs(oldNoData.doubleValue()
207: - actualValue) <= EPS)
208: iterator.setSample(newNoData);
209: else
210: iterator.setSample(actualValue);
211:
212: } while (!iterator.nextPixelDone());
213: } while (!iterator.nextLineDone());
214:
215: }
216:
217: /////////////////////////////////////////////////////////////////////////////////
218: //////// ////////
219: //////// REGISTRATION OF "NoDataReplacer" IMAGE OPERATION ////////
220: //////// ////////
221: /////////////////////////////////////////////////////////////////////////////////
222: /**
223: * The operation descriptor for the "NoDataReplacer" operation. This operation
224: * is used to change the format of an Image while replacing the NoData value with a new
225: * one as requested. The difference between this method and the usual format operation presents
226: * in JAI is the possibility to replace the NoData value directly when it is like Double.NaN or
227: * Float.NaN.
228: *
229: */
230: private static final class NoDataReplacerDescriptor extends
231: OperationDescriptorImpl {
232: /**
233: * Comment for <code>serialVersionUID</code>
234: */
235: private static final long serialVersionUID = 1L;
236:
237: /**
238: * Construct the descriptor.
239: */
240: public NoDataReplacerDescriptor() {
241: super (
242: new String[][] {
243: { "GlobalName", OPERATION_NAME },
244: { "LocalName", OPERATION_NAME },
245: { "Vendor", "Geotools 2" },
246: { "Description",
247: "Nodata replacement and layout adjustment." },
248: { "DocURL", "http://www.geotools.org/" },
249: { "Version", "1.0" } },
250: new String[] { RenderedRegistryMode.MODE_NAME }, 1,
251: new String[] { "oldNoData", "newNoData", "EPS" }, // Argument names
252: new Class[] { Number.class, Short.class,
253: Double.class }, // Argument classes
254: new Object[] { new Double(Double.NaN),
255: new Short((short) -9999),
256: new Double(10.0E-6) }, // Default values for parameters,
257: null // No restriction on valid parameter values.
258: );
259: }
260:
261: /**
262: * Returns {@code true} if the parameters are valids. This implementation check
263: * that the number of bands in the source image is equals to the number of supplied
264: * sample dimensions, and that all sample dimensions has categories.
265: *
266: * @param modeName The mode name (usually "Rendered").
267: * @param param The parameter block for the operation to performs.
268: * @param message A buffer for formatting an error message if any.
269: */
270: protected boolean validateParameters(final String modeName,
271: final ParameterBlock param, final StringBuffer message) {
272: if (!super .validateParameters(modeName, param, message)) {
273: return false;
274: }
275: try {
276: // param.
277: // final RenderedImage source = (RenderedImage) param.getSource(0);
278: // final Number oldNoData= (Number) param.getObjectParameter(0);
279: // final Number newNoData= (Number) param.getObjectParameter(1);
280: // final Double EPS= (Double) param.getObjectParameter(1);
281: } catch (Exception e) {
282: message.append(e.getMessage());
283: return false;
284: }
285: return true;
286: }
287: }
288:
289: /**
290: * The {@link RenderedImageFactory} for the "SampleTranscode" operation.
291: */
292: private static final class NoDataReplacerCRIF extends CRIFImpl {
293: /**
294: * Creates a {@link RenderedImage} representing the results of an imaging
295: * operation for a given {@link ParameterBlock} and {@link RenderingHints}.
296: */
297: public RenderedImage create(final ParameterBlock param,
298: final RenderingHints hints) {
299: final RenderedImage source = (RenderedImage) param
300: .getSource(0);
301:
302: final Number oldNoData = (Number) param
303: .getObjectParameter(0);
304: final Short newNoData = (Short) param.getObjectParameter(1);
305: final Double EPS = (Double) param.getObjectParameter(2);
306: return new NoDataReplacerOpImage(source, oldNoData,
307: newNoData, EPS, hints);
308: }
309:
310: }
311:
312: /**
313: * Register the "SampleTranscode" image operation to the operation registry of
314: * the specified JAI instance. This method is invoked by the static initializer
315: * of {@link GridSampleDimension}.
316: */
317: public static void register(final JAI jai) {
318: final OperationRegistry registry = jai.getOperationRegistry();
319: try {
320: registry.registerDescriptor(new NoDataReplacerDescriptor());
321: registry.registerFactory(RenderedRegistryMode.MODE_NAME,
322: OPERATION_NAME, "gce.geotools.org",
323: new NoDataReplacerCRIF());
324: } catch (IllegalArgumentException exception) {
325: final LogRecord record = Logging.format(Level.SEVERE,
326: LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1,
327: OPERATION_NAME);
328: record.setSourceClassName("GridSampleDimension");
329: record.setSourceMethodName("<classinit>");
330: record.setThrown(exception);
331: AbstractGridCoverage.LOGGER.log(record);
332: }
333: }
334: }
|