I'm fairly new with GeoTools. I'm using it in order to generate GeoTIFF files that have a single band, with values between -1 and 1, or NaN.

An example of such a file is available here: https://m-innvoincy.box.com/s/ow1fentcgddgdeisfwj4i44yraeqkh4l

If you load this into QGIS or something similar, it should look like this (by default):

Grayscale rendering of the values in the GeoTIFF

The next step, which I'm struggling with, is to generate a multi-color PNG file, that will be a visual representation of the raw data in the GeoTIFF (I'm ignoring the geography for now). I would obviously need to specify a mapping between ranges of values in the GeoTIFF and RGBA values. NaN values should be mapped into the alpha channel ('transparent' pixels).

How a possible output PNG file might look like

I would like to do it with GeoTools, but haven't found a way to do it yet. It's important to note that the code should run on the server-side, there is no Map object and no GUI, Swing or otherwise, should be involved.


While you could probably do this by rolling a set of nested loops to go over the individual pixels and hand calculate the RGB values you want to stuff into the image and save it out as a PNG, it is easier to stuff the coverage into a MapContent, adding a Style with a ColorMap and then ask the renderer to draw it to an empty image.

package spike;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.GridFormatFinder;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.gce.geotiff.GeoTiffFormat;
import org.geotools.map.GridCoverageLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.ColorMap;
import org.geotools.styling.ColorMapEntry;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.SLD;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.opengis.filter.FilterFactory2;

public class TiffToPng {

  public static void main(String[] args) throws IllegalArgumentException, IOException {
    // TODO Auto-generated method stub
    File input = new File(args[0]);


  private static void convert(File input) throws IllegalArgumentException, IOException {
    AbstractGridFormat format = GridFormatFinder.findFormat(input);
    // working around a bug/quirk in geotiff loading via format.getReader which
    // doesn't set this correctly
    Hints hints = null;
    if (format instanceof GeoTiffFormat) {
      hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);

    AbstractGridCoverage2DReader reader = format.getReader(input, hints);
    GridCoverage2D grid = reader.read(null);

    BufferedImage image = new BufferedImage(grid.getGridGeometry().getGridRange2D().width,
        grid.getGridGeometry().getGridRange2D().height, BufferedImage.TYPE_4BYTE_ABGR);

    MapContent mapContent = new MapContent();
    Layer rasterLayer = new GridCoverageLayer(grid, createStyle(1,-0.4,0.2));
    GTRenderer draw = new StreamingRenderer();
    Graphics2D graphics = image.createGraphics();
    draw.paint(graphics, grid.getGridGeometry().getGridRange2D(), mapContent.getMaxBounds());
    File out = new File("test.png");
    ImageIO.write(image, "PNG", out);


  private static Style createStyle(int band, double min, double max) {

    FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
    StyleFactory sf = CommonFactoryFinder.getStyleFactory();

    RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
    ColorMap cMap = sf.createColorMap();
    ColorMapEntry start = sf.createColorMapEntry();
    ColorMapEntry end = sf.createColorMapEntry();

    Style style = SLD.wrapSymbolizers(sym);

    return style;

With a bit more effort I could probably work out how to write the image back out as a PNG+WLD file but it isn't clear if that is what you need. Anyway this is what the result looks like:

enter image description here


For anyone who does want a world file etc to go with the output png here is the code:

File out2 = new File("test2.png");
final WorldImageWriter wiWriter = new WorldImageWriter(out2);

// writing parameters for png
final Format writerFormat = wiWriter.getFormat();

// setting write parameters
final ParameterValueGroup params = writerFormat.getWriteParameters();
final GeneralParameterValue[] gpv = {
GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null);
GridCoverage2D coverage = factory.create("name", image, grid.getEnvelope());
// writing
wiWriter.write(coverage , gpv);
//write out the prj file too
File prj = new File("test2.prj");
PrintWriter pw = new PrintWriter(prj);
| improve this answer | |
  • Looks very promising, thanks. Just to make sure: can this work headless (i.e., on the server-side, without a GUI)? – yby Sep 7 '18 at 6:15
  • 1
    Should be fine on a headless machine, for example many GeoServer instances are headless – Ian Turton Sep 7 '18 at 8:12
  • Yep, works like a charm. Thanks so much for the working example! Looking at the code, I'm wondering how would it work if I had more than a one band in the input, and I'd want to render each band to it's on PNG? The 'band' parameter of createStyle isn't used, and I don't see any reference to a band number when accessing the grid. – yby Sep 7 '18 at 8:18
  • 1
    Whoops, I deleted that code as it wasn't needed since there was only one band. docs.geotools.org/latest/userguide/tutorial/raster/image.html shows an example of band selection - docs.geoserver.org/latest/en/user/styling/sld/reference/… discusses the theory – Ian Turton Sep 7 '18 at 8:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.