需要帮助从裁剪的 geotiff 渲染地图框

Need help rendering a JMapFrame froma cropped geotiff

我是 geotools 的新手,对 Java 有点陌生。我创建了一个程序,可以读取 geotiff、裁剪它,然后使用 JavaFX 将裁剪后的图像渲染到 ImageView 中。现在,我想在渲染图像上添加地理点作为图层。我已经创建了一个带有标题的 MapContent。我遇到问题的地方是渲染一个 JMapFrame 来测试正在传递的数据。我正在尝试创建并添加裁剪图像的 GridCoverageLayer。我无法让 JMapFrame 渲染图像,它似乎陷入了循环。我怀疑问题是将图层的样式设置为 NULL。如果这是问题所在,我该如何创建基于光栅的样式?我已经尝试阅读 Geotools API 和教程,但我有一半时间无法做出正面或反面...... 我的最终目标是使用 JavaFX 而不是 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);
        }
}

编辑

图像的 GdalInfo

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) 

更新

您的 tiff 不是一个简单的光栅,它包含一个调色板图像 (ColorInterp=Palette),因此每个像素包含一个介于 0-255 之间的字节,它映射到一种颜色。所以你的目标是象征图像将不起作用,因为像素值和颜色之间没有线性关系。要在 GeoTools 中显示此图像,您需要一个空的 RasterSymbolizer,这就是 createGreyscaleStyle() 方法的作用。我用调色板图像对其进行了测试,它对我来说效果很好(注意波段从 1 开始计数,而你只有一个波段)。

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);
}

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 第 4 节。或者,您可以导入包含样式的 SLD 文件。

 /**
     * 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);
    }
}