/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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.renderer.lite.gridcoverage2d; import java.awt.Color; import java.awt.image.DataBuffer; import java.awt.image.LookupTable; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.util.logging.Level; import java.util.logging.Logger; import javax.media.jai.JAI; import javax.media.jai.LookupTableJAI; import javax.media.jai.OperationDescriptor; import javax.media.jai.RenderedOp; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.TypeMap; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.factory.Hints; import org.geotools.referencing.piecewise.Domain1D; import org.geotools.renderer.i18n.ErrorKeys; import org.geotools.renderer.i18n.Errors; import org.geotools.renderer.i18n.Vocabulary; import org.geotools.renderer.i18n.VocabularyKeys; import org.geotools.styling.ColorMap; import org.geotools.styling.ColorMapEntry; import org.geotools.styling.StyleVisitor; import org.geotools.util.SimpleInternationalString; import org.geotools.util.logging.Logging; import org.opengis.coverage.SampleDimensionType; import org.opengis.coverage.grid.GridCoverage; import org.opengis.util.InternationalString; /** * This {@link CoverageProcessingNode} is responsible for visiting the supplied {@link ColorMapTransform} and applying it to the source {@link GridCoverage2D} . <p> <strong>What we support and how do we implement it</strong> <p> A ColorMapTransform is created in order to map categories to colors on a single band coverage (or on the visible band of multiband coverage). <p> In this implementation we allow users to use either 256 or 65536 colors via the creation of a paletted image with s suitable palette derived from the single {@link ColorMapEntry} that make up the {@link ColorMapTransform} . * @author Simone Giannecchini, GeoSolutions * @see ColorMapTransform */ class ColorMapNode extends StyleVisitorCoverageProcessingNodeAdapter implements StyleVisitor, CoverageProcessingNode { /* * (non-Javadoc) * @see CoverageProcessingNode#getName() */ public InternationalString getName() { return Vocabulary.formatInternational(VocabularyKeys.COLOR_MAP); } /** {@link Logger} for this class. */ private final static Logger LOGGER = Logging.getLogger(ColorMapNode.class.getName()); static { try { if (JAI.getDefaultInstance().getOperationRegistry().getDescriptor( OperationDescriptor.class, RasterClassifier.OPERATION_NAME) == null) RasterClassifier.register(JAI.getDefaultInstance()); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); } } /** * Stores the type of {@link ColorMapTransform} we want to use for this node. * * * <p> * Possible types are * <ol> * <li>TYPE_RAMP which will build a ramp f colors using linear * interpolation</li> * <li>TYPE_INTERVALS which would do classifications, i.e. would map * intervals to colors</li> * <li>TYPE_VALUES, which would do single value slicing</li> * </ol> * */ private int type; /** * The {@link Domain1D} that we build while parsing the various * {@link ColorMapEntry}s provided by the {@link ColorMapTransform} we visit. This * {@link LinearColorMap} we'll help us buld the colormapped * image. */ private LinearColorMap colorMapTransform; /** * Do we want 16 bits or 8 bits colormap? * @uml.property name="extendedColors" */ private boolean extendedColors; /** * Visits the provided {@link ColorMapTransform} and build up a {@link Domain1D} * for later creation of a palette rendering for this coverage. */ public synchronized void visit(ColorMap colorMap) { // // // // This allows this node to work in store-and-forward way in case we had // nothing to do // // // if (colorMap == null) return; // ///////////////////////////////////////////////////////////////////// // // Get the type and check it. In case the type or the entries are // invalid we skip this node. // // ///////////////////////////////////////////////////////////////////// this.type = colorMap.getType(); this.extendedColors = colorMap.getExtendedColors(); final ColorMapEntry[] cmEntries = colorMap.getColorMapEntries(); if (cmEntries != null && cmEntries.length > 0) { // ///////////////////////////////////////////////////////////////////// // // Check the source coverage and, if it has more than one band // reduce it to 1 band using the visible band. // // If do not manage to do so, let's throw an exception since as per // Craig Bruce email it is an error to apply a ColoMap to a // multiband coverage. // // ///////////////////////////////////////////////////////////////////// final CoverageProcessingNode source=getSource(0); ensureSourceNotNull(source, "ColorMapNode"); final GridCoverage2D sourceCoverage = (GridCoverage2D) source.getOutput(); ensureSourceNotNull(sourceCoverage, "ColorMapNode"); final int numSD = sourceCoverage.getNumSampleDimensions(); if (numSD>1) throw new IllegalArgumentException( Errors.format(ErrorKeys.BAD_BAND_NUMBER_$1,Integer.valueOf(numSD))); // ///////////////////////////////////////////////////////////////////// // // Check the sample dimension we are going to use for NoData // categories. // // It is important to have such categories since we might have holes // in the categories we are going to build hence it is important to // have a valid NoDataValue that we can use. // // ///////////////////////////////////////////////////////////////////// final GridSampleDimension candidateSD = (GridSampleDimension) sourceCoverage.getSampleDimension(0); double[] candidateNoDataValues = preparaNoDataValues(candidateSD); // ///////////////////////////////////////////////////////////////////// // // Main Loop // // ///////////////////////////////////////////////////////////////////// //TODO MAKE THE COLORS CONFIGURABLE final SLDColorMapBuilder builder = new SLDColorMapBuilder(); builder.setExtendedColors(this.extendedColors) .setLinearColorMapType(this.type) .setNumberColorMapEntries(cmEntries.length) .setColorForValuesToPreserve(new Color(0, 0, 0, 0)) .setGapsColor(new Color(0, 0, 0, 0)); for (int i = 0; i < cmEntries.length; i++) { builder.addColorMapEntry(cmEntries[i]); } // ///////////////////////////////////////////////////////////////////// // // Create the list of no data colorMapTransform domain elements. Note that all of them // // ///////////////////////////////////////////////////////////////////// if(candidateNoDataValues!=null&&candidateNoDataValues.length>0){ final LinearColorMapElement noDataCategories[] = new LinearColorMapElement[candidateNoDataValues.length]; for (int i = 0; i < noDataCategories.length; i++) { builder.addValueToPreserve(candidateNoDataValues[i]); } } // ///////////////////////////////////////////////////////////////////// // // Create the list of colorMapTransform categories // // ///////////////////////////////////////////////////////////////////// colorMapTransform = builder.buildLinearColorMap(); } else this.type = -1; } /** * @param candidateSD * @return * @throws IllegalStateException */ private static double[] preparaNoDataValues(final GridSampleDimension candidateSD) throws IllegalStateException { double[] candidateNoDataValues = candidateSD.getNoDataValues(); // if no nodata categories are ready we'll add a fictitious one // @todo TODO make this code configurable if (candidateNoDataValues == null) { candidateNoDataValues= new double[1]; final SampleDimensionType sdType = candidateSD.getSampleDimensionType(); final int dataBufferType=TypeMap.getDataBufferType(sdType); switch(dataBufferType){ case DataBuffer.TYPE_SHORT: candidateNoDataValues[0]=Short.MIN_VALUE; break; case DataBuffer.TYPE_INT: candidateNoDataValues[0]=Integer.MIN_VALUE; break; case DataBuffer.TYPE_FLOAT: candidateNoDataValues[0]=Float.NaN; break; case DataBuffer.TYPE_DOUBLE:case DataBuffer.TYPE_UNDEFINED: candidateNoDataValues[0]=Double.NaN; break; default://BYTE, USHORT candidateNoDataValues=null; break; } } return candidateNoDataValues; } /** * Default constructor for the {@link ColorMapNode} processing node. */ public ColorMapNode() { this(null); } /** * Default constructor for the {@link ColorMapNode} processing node. */ public ColorMapNode(Hints hints) { super( 1, hints, SimpleInternationalString.wrap("ColorMapNode"), SimpleInternationalString .wrap("Node which applies a ColorMapTransform following SLD 1.0 spec.")); } /** * Note that the color map can be applied only to a single band hence, in * principle, applying the {@link ColorMapTransform} element to a coverage with more * than one band is an error. */ protected GridCoverage2D execute() { /////////////////////////////////////////////////////////////////// // //get the source for this node and ensure it is correct // /////////////////////////////////////////////////////////////////// final CoverageProcessingNode sourceNode = getSource(0); ensureSourceNotNull(sourceNode, this.getName().toString()); final GridCoverage2D sourceCoverage = (GridCoverage2D) sourceNode.getOutput(); ensureSourceNotNull(sourceCoverage, this.getName().toString()); /////////////////////////////////////////////////////////////////// // //now apply the colormap if one exists // /////////////////////////////////////////////////////////////////// if (colorMapTransform != null) { //get input image final RenderedImage sourceImage = sourceCoverage.getRenderedImage(); ensureSourceNotNull(sourceImage, this.getName().toString()); //prepare the colorMapTransform operation final ParameterBlock pbj = new ParameterBlock(); pbj.addSource(sourceImage).add(colorMapTransform); final RenderedOp classified = JAI.create(RasterClassifier.OPERATION_NAME,pbj); //// // // prepare the output coverage by specifying its bands // //// final int outputChannels=classified.getColorModel().getNumComponents(); final int numBands=classified.getSampleModel().getNumBands(); assert outputChannels==1||outputChannels==3||outputChannels==4; final GridSampleDimension [] sd= new GridSampleDimension[numBands]; for(int i=0;i<numBands;i++) sd[i]= new GridSampleDimension(TypeMap.getColorInterpretation(classified.getColorModel(), i).name()); //// // // Create the the output coverage by preserving its gridgeometry and its bands // //// return getCoverageFactory().create( "color_mapped_"+sourceCoverage.getName().toString(), classified, sourceCoverage.getGridGeometry(), sd, new GridCoverage[]{sourceCoverage}, sourceCoverage.getProperties() ); } return sourceCoverage; } /** * It tells me whether or not we are using extended colors for this colormap. * @return the extendedColors * @uml.property name="extendedColors" */ public synchronized boolean isExtendedColors() { return extendedColors; } }