1

I'm new to geotools and somewhat new to Java. I have created a program that can read a geotiff, crop it, and, using JavaFX, render the cropped image into an ImageView. Now, I'd like to add geographical points as layers on the rendered image. I've accomplished creating a MapContent with a title. Where I am having issues, is rendering a JMapFrame to test the data is being passed. I am trying to create and add a GridCoverageLayer of the cropped image. I cannot get the JMapFrame to render the image, it appears to be stuck in a loop. I am suspecting the issue is setting the Style of the Layer to NULL. If this is the issue, how do I create a raster based Style? I've tried reading the Geotools API and tutorials, and I just can't make heads or tails half the time.... My ultimate goal is render the map with symbols with JavaFX instead of AWT.

import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.processing.CoverageProcessor;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.geometry.GeneralDirectPosition;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.GridCoverageLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.referencing.GeodeticCalculator;
import org.geotools.swing.JMapFrame;
import org.geotools.util.factory.Hints;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

import java.io.File;
import java.io.IOException;

public class Processor {

    private static void getImage(File file, double NE_lon, double NE_lat, double SW_lon, double SW_lat) throws IOException, TransformException{

            //Create the coverage processor and create the crop operation
            final CoverageProcessor processor = new CoverageProcessor();
            final ParameterValueGroup param = processor.getOperation("CoverageCrop").getParameters();

            //Read the TIFF, create the coverage/grid, get the CRS, and get the image envelope
            GeoTiffReader reader = new GeoTiffReader(file, new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,Boolean.TRUE));
            GridCoverage2D coverage = reader.read(null);
            CoordinateReferenceSystem inCRS = coverage.getCoordinateReferenceSystem();
            GeneralEnvelope inEnvelope = (GeneralEnvelope) coverage.getEnvelope();

            //Get the image envelope min/max coordinates
            GeneralDirectPosition inMaxDP = (GeneralDirectPosition) inEnvelope.getUpperCorner();
            GeneralDirectPosition inMinDP = (GeneralDirectPosition) inEnvelope.getLowerCorner();

            //Calculate the crop cartesian min/max coordinates
            GeodeticCalculator calc = new GeodeticCalculator(inCRS);
            calc.setStartingGeographicPoint(NE_lon,NE_lat);
            GeneralDirectPosition cropMaxDP = (GeneralDirectPosition) calc.getStartingPosition();
            calc.setStartingGeographicPoint(SW_lon,SW_lat);
            GeneralDirectPosition cropMinDP = (GeneralDirectPosition) calc.getStartingPosition();

            //Output to console the original and cropped cartesian min/max coordinates
            System.out.println("Coordinate system: ");
            System.out.println("NE (max) corner (meters from meridian (x), origin (y): "+inMaxDP);
            System.out.println("SW (min) corner (meters from meridian (x), origin (y): "+inMinDP);
            System.out.println();
            System.out.println("NE (max) trim corner (lon,lat): "+NE_lon+","+NE_lat);
            System.out.println("SW (min) trim corner (lon,lat): "+SW_lon+","+SW_lat);
            System.out.println("NE (max) trim corner (meters from meridian (x), origin (y): "+cropMaxDP);
            System.out.println("SW (min) trim corner (meters from meridian (x), origin (y): "+cropMinDP);
            System.out.println();

            //Create the crop envelope size and crop the image envelope
            final ReferencedEnvelope crop = new ReferencedEnvelope(
                    cropMinDP.getOrdinate(0),
                    cropMaxDP.getOrdinate(0),
                    cropMinDP.getOrdinate(1),
                    cropMaxDP.getOrdinate(1),
                    inCRS);

            //Set the Processor to look at the Coverage2D image and crop to the ReferenceEnvelope set
            param.parameter("Source").setValue( coverage );
            param.parameter("Envelope").setValue( crop );
            GridCoverage2D cropCoverage = (GridCoverage2D) processor.doOperation(param);

            //Create a Map with layers
            MapContent map = new MapContent();
            map.setTitle("Detroit");
            Layer coverageLayer = new GridCoverageLayer(cropCoverage,null,"Background");
            map.addLayer(coverageLayer);

            JMapFrame.showMap(map);

            //Generate a BufferedImage of the GridCoverage2D
    //        PlanarImage croppedRenderedImageImage = (PlanarImage) cropCoverage.getRenderedImage();
    //        BufferedImage image = croppedRenderedImageImage.getAsBufferedImage();
    //        System.out.println("Image type: "+image.getType());
    //        System.out.println("Image height: "+image.getHeight());
    //        System.out.println("Image width: "+image.getWidth());

            //Write crop to file system
            /*File outFile = new File("/home/greg/Software_Projects/JavaProjects/charts/Detroit_98/Detroit_SEC_98.tif");
            GeoTiffWriter writer = new GeoTiffWriter(outFile,new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,Boolean.TRUE));
            writer.write(cropped,null);*/
        }

        public static void main(String[] args) throws IOException, TransformException {
            File inFile = new File("/home/greg/Software_Projects/JavaProjects/charts/Detroit_98/Detroit SEC 98.tif");
            getImage(inFile,-81,42,-82.5,41);
        }
}

EDIT

GdalInfo for image

Image Structure Metadata: INTERLEAVE=BAND 
Corner Coordinates: Upper Left ( -84165.569, -73866.808) ( 82d 0'29.23"W, 41d29'48.29"N) 
Lower Left ( -84165.569, -129071.257) ( 82d 0' 0.36"W, 40d59'59.09"N) 
Upper Right ( -41702.491, -73866.808) ( 81d29'58.28"W, 41d30' 0.88"N) 
Lower Right ( -41702.491, -129071.257) ( 81d29'43.98"W, 41d 0'11.57"N) 
Center ( -62934.030, -101469.032) ( 81d45' 2.94"W, 41d15' 0.94"N) 
Band 1 Block=1003x1 Type=Byte, ColorInterp=Palette NoData Value=0 Color Table (RGB with 256 entries) 
Ian Turton
  • 10,018
  • 1
  • 28
  • 47
flyinggreg
  • 79
  • 1
  • 9

1 Answers1

0

Update

Your tiff is not a simple raster, it contains a paletted image (ColorInterp=Palette) so each pixel contains a single byte between 0-255 which maps to a colour. So you aim to symbolize the image will not work as there is not a linear relationship between pixel values and colours. To display this image in GeoTools you need an empty RasterSymbolizer which is what the createGreyscaleStyle() method does. I've tested it with a paletted image and it works fine for me (note bands count from 1 and you only have one band).

private Style createGreyscaleStyle(int band) {
    ContrastEnhancement ce = new ContrastEnhancementImpl();
    SelectedChannelType sct = sf.createSelectedChannelType(String.valueOf(band), ce);

    RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
    ChannelSelection sel = sf.channelSelection(sct);
    sym.setChannelSelection(sel);

    return SLD.wrapSymbolizers(sym);
}

Section 4 of the Image Tutorial shows how to create a colour raster SLD - you can't just use a NULL style as GeoTools will now have no idea of how to convert the bands to an image. There is a fuller description of possible RasterSymbolizer options in the SLD reference in the GeoServer manual. Alternatively, you can import an SLD file containing the style.

 /**
     * This method examines the names of the sample dimensions in the provided coverage looking for
     * "red...", "green..." and "blue..." (case insensitive match). If these names are not found it
     * uses bands 1, 2, and 3 for the red, green and blue channels. It then sets up a raster
     * symbolizer and returns this wrapped in a Style.
     *
     * @return a new Style object containing a raster symbolizer set up for RGB image
     */
    private Style createRGBStyle() {
        GridCoverage2D cov = null;
        try {
            cov = reader.read(null);
        } catch (IOException giveUp) {
            throw new RuntimeException(giveUp);
        }
        // We need at least three bands to create an RGB style
        int numBands = cov.getNumSampleDimensions();
        if (numBands < 3) {
            return null;
        }
        // Get the names of the bands
        String[] sampleDimensionNames = new String[numBands];
        for (int i = 0; i < numBands; i++) {
            GridSampleDimension dim = cov.getSampleDimension(i);
            sampleDimensionNames[i] = dim.getDescription().toString();
        }
        final int RED = 0, GREEN = 1, BLUE = 2;
        int[] channelNum = {-1, -1, -1};
        // We examine the band names looking for "red...", "green...", "blue...".
        // Note that the channel numbers we record are indexed from 1, not 0.
        for (int i = 0; i < numBands; i++) {
            String name = sampleDimensionNames[i].toLowerCase();
            if (name != null) {
                if (name.matches("red.*")) {
                    channelNum[RED] = i + 1;
                } else if (name.matches("green.*")) {
                    channelNum[GREEN] = i + 1;
                } else if (name.matches("blue.*")) {
                    channelNum[BLUE] = i + 1;
                }
            }
        }
        // If we didn't find named bands "red...", "green...", "blue..."
        // we fall back to using the first three bands in order
        if (channelNum[RED] < 0 || channelNum[GREEN] < 0 || channelNum[BLUE] < 0) {
            channelNum[RED] = 1;
            channelNum[GREEN] = 2;
            channelNum[BLUE] = 3;
        }
        // Now we create a RasterSymbolizer using the selected channels
        SelectedChannelType[] sct = new SelectedChannelType[cov.getNumSampleDimensions()];
        ContrastEnhancement ce = sf.contrastEnhancement(ff.literal(1.0), ContrastMethod.NORMALIZE);
        for (int i = 0; i < 3; i++) {
            sct[i] = sf.createSelectedChannelType(String.valueOf(channelNum[i]), ce);
        }
        RasterSymbolizer sym = sf.getDefaultRasterSymbolizer();
        ChannelSelection sel = sf.channelSelection(sct[RED], sct[GREEN], sct[BLUE]);
        sym.setChannelSelection(sel);

        return SLD.wrapSymbolizers(sym);
    }
}
Ian Turton
  • 10,018
  • 1
  • 28
  • 47
  • that's what I was suspecting. The problem is, I only read one sample dimension. The way I'm reading this tutorial, that will return NULL because it's designed to read three for RGB. // We need at least three bands to create an RGB style int numBands = cov.getNumSampleDimensions(); if (numBands < 3) { return null; Hence, my layer was NULL. If I read the image into a BufferedImage and getType, it returns type 13. – flyinggreg Oct 23 '19 at 12:20
  • In that case look at the grey scale sld in the same tutorial – Ian Turton Oct 23 '19 at 15:32
  • thanks, I'll try that. Since I'm pretty much a novice, even though my coverage is a color rasterized TIFF and reporting one dimension, is it possible to pull out the RGB bands? – flyinggreg Oct 23 '19 at 16:23
  • I tried both methods. The greyscale faults saying "band # is invalid. # meaning the band# I attempted to read...it didn't matter what number was set. I even attemted GridCoverage.getDimension. So, I then attempted the RGB method. It didn not fault, but the JMap again hung in a loop rendering the map. Any ideas? – flyinggreg Oct 23 '19 at 20:30
  • can you add the `gdalinfo` output of your geotiff to the question – Ian Turton Oct 24 '19 at 08:28
  • ,here's the gdalinfo grabbed from QGis: Image Structure Metadata: INTERLEAVE=BAND Corner Coordinates: Upper Left ( -84165.569, -73866.808) ( 82d 0'29.23"W, 41d29'48.29"N) Lower Left ( -84165.569, -129071.257) ( 82d 0' 0.36"W, 40d59'59.09"N) Upper Right ( -41702.491, -73866.808) ( 81d29'58.28"W, 41d30' 0.88"N) Lower Right ( -41702.491, -129071.257) ( 81d29'43.98"W, 41d 0'11.57"N) Center ( -62934.030, -101469.032) ( 81d45' 2.94"W, 41d15' 0.94"N) Band 1 Block=1003x1 Type=Byte, ColorInterp=Palette NoData Value=0 Color Table (RGB with 256 entries) – flyinggreg Oct 24 '19 at 18:17
  • 1
    AWESOME!! It worked!! Thanks for the help and patience! Now that I have the raster background, I can play around with GeoTools and try to add layers. The only way to learn this is practice, fail, and ask questions to get it right. Thanks, again! – flyinggreg Oct 25 '19 at 14:11