org.geotools.process.raster.FootprintExtractionProcessTest.java Source code

Java tutorial

Introduction

Here is the source code for org.geotools.process.raster.FootprintExtractionProcessTest.java

Source

/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2014, 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.process.raster;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.awt.image.Raster;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.test.TestData;
import org.geotools.util.Range;
import org.geotools.util.Utilities;
import org.junit.Before;
import org.junit.Test;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.InStream;
import com.vividsolutions.jts.io.InputStreamInStream;
import com.vividsolutions.jts.io.OutStream;
import com.vividsolutions.jts.io.OutputStreamOutStream;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.WKTWriter;

/**
 * Tests for the raster to vector FootprintExtractionProcess.
 * 
 * @author Daniele Romagnoli, GeoSolutions SAS
 * 
 */
public class FootprintExtractionProcessTest {

    private static final String THE_GEOM = "the_geom";

    private static final String CREATE_SPATIAL_INDEX = "create spatial index";

    private static final double TOLERANCE = 1.0e-12;

    private FootprintExtractionProcess process;

    /** A reference geometry being extracted from the cloud file by excluding only BLACK pixels */
    private Geometry referenceGeometry;

    private File cloudFile;

    private File islandFile;

    static enum WritingFormat {
        WKB {
            @Override
            void write(Geometry geometry, File outputFile, CoordinateReferenceSystem crs) throws IOException {
                final WKBWriter wkbWriter = new WKBWriter(2);
                final OutputStream outputStream = new FileOutputStream(outputFile);
                final BufferedOutputStream bufferedStream = new BufferedOutputStream(outputStream);
                final OutStream outStream = new OutputStreamOutStream(bufferedStream);
                try {
                    wkbWriter.write(geometry, outStream);
                } finally {
                    IOUtils.closeQuietly(bufferedStream);
                }
            }
        },
        WKT {
            @Override
            void write(Geometry geometry, File outputFile, CoordinateReferenceSystem crs) throws IOException {
                final WKTWriter wktWriter = new WKTWriter(2);
                final StringWriter wkt = new StringWriter();
                BufferedWriter bufferedWriter = new BufferedWriter(wkt);
                try {
                    wktWriter.write(geometry, bufferedWriter);
                } finally {
                    IOUtils.closeQuietly(bufferedWriter);
                }

                // write to file
                if (outputFile != null) {

                    bufferedWriter = new BufferedWriter(new FileWriter(outputFile));
                    try {
                        bufferedWriter.write(wkt.toString());
                    } finally {
                        IOUtils.closeQuietly(bufferedWriter);
                    }
                }
            }
        },
        SHAPEFILE {
            @Override
            void write(Geometry geometry, File outputFile, CoordinateReferenceSystem crs) throws IOException {

                // create feature type
                final SimpleFeatureTypeBuilder featureTypeBuilder = new SimpleFeatureTypeBuilder();
                featureTypeBuilder.setName("raster2vector");
                featureTypeBuilder.setCRS(crs);
                featureTypeBuilder.add(THE_GEOM, Polygon.class);
                featureTypeBuilder.add("cat", Integer.class);
                SimpleFeatureType featureType = featureTypeBuilder.buildFeatureType();

                // Preparing the collection
                final ListFeatureCollection collection = new ListFeatureCollection(featureType);
                final String typeName = featureType.getTypeName();

                // Creating the feature
                final SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
                final Object[] values = new Object[] { geometry, 0 };
                featureBuilder.addAll(values);
                final SimpleFeature feature = featureBuilder.buildFeature(typeName + '.' + 0);

                // adding the feature to the collection
                collection.add(feature);

                // create shapefile
                final DataStoreFactorySpi factory = new ShapefileDataStoreFactory();

                // Preparing creation param
                final Map<String, Serializable> params = new HashMap<String, Serializable>();
                params.put(CREATE_SPATIAL_INDEX, Boolean.TRUE);
                params.put("url", DataUtilities.fileToURL(outputFile));

                final ShapefileDataStore ds = (ShapefileDataStore) factory.createNewDataStore(params);
                ds.createSchema(featureType);
                if (crs != null) {
                    ds.forceSchemaCRS(crs);
                }

                // Write the features to the shapefile
                Transaction transaction = new DefaultTransaction("create");

                FeatureSource source = ds.getFeatureSource(ds.getTypeNames()[0]);

                if (source instanceof FeatureStore) {
                    FeatureStore store = (FeatureStore) source;

                    store.setTransaction(transaction);
                    try {
                        store.addFeatures(collection);
                        transaction.commit();

                    } catch (Exception e) {
                        transaction.rollback();

                    } finally {
                        try {
                            transaction.close();
                        } catch (IOException ioe) {

                        }
                    }
                }
                ds.dispose();

            }
        };

        abstract void write(Geometry geometry, File outputFile, CoordinateReferenceSystem crs) throws IOException;
    }

    @Before
    public void setup() throws IOException, ParseException {
        process = new FootprintExtractionProcess();
        cloudFile = TestData.file(this, "cloud.tif");
        islandFile = TestData.file(this, "island.tif");
        final File geometryFile = TestData.file(this, "cloud.wkt");
        referenceGeometry = wktRead(geometryFile);
    }

    private static Geometry wktRead(File geometryFile) throws FileNotFoundException, ParseException {
        FileReader fileReader = null;
        try {
            WKTReader wktReader = new WKTReader();
            fileReader = new FileReader(geometryFile);
            return wktReader.read(fileReader);
        } finally {
            IOUtils.closeQuietly(fileReader);
        }
    }

    private static Geometry wkbRead(File geometryFile) throws ParseException, IOException {
        BufferedInputStream bufferedStream = null;
        try {
            WKBReader wkbReader = new WKBReader();
            final InputStream inputStream = new FileInputStream(geometryFile);
            bufferedStream = new BufferedInputStream(inputStream);
            final InStream inStream = new InputStreamInStream(bufferedStream);
            return wkbReader.read(inStream);
        } finally {
            IOUtils.closeQuietly(bufferedStream);
        }
    }

    @Test
    public void cloudExtractionTest() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(cloudFile);
            cov = reader.read(null);
            SimpleFeatureCollection fc = process.execute(cov, null, 10d, false, null, true, true, null, null);
            assertEquals(1, fc.size());

            iter = fc.features();

            SimpleFeature feature = iter.next();
            MultiPolygon geometry = (MultiPolygon) feature.getDefaultGeometry();
            assertTrue(referenceGeometry.equalsExact(geometry, TOLERANCE));
        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    @Test
    public void cloudExtractionSimplified() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(cloudFile);
            cov = reader.read(null);
            SimpleFeatureCollection fc = process.execute(cov, null, 10d, true, 4d, true, true, null, null);
            assertEquals(2, fc.size());
            iter = fc.features();

            // Getting the main Footprint
            SimpleFeature feature = iter.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            double fullArea = geometry.getArea();

            // Getting to the simplified Footprint
            feature = (SimpleFeature) iter.next();
            geometry = (Geometry) feature.getDefaultGeometry();
            double simplifiedArea = geometry.getArea();

            // area are different and polygons are different too
            assertTrue(Math.abs(simplifiedArea - fullArea) > 0);
            assertFalse(referenceGeometry.equalsExact(geometry, TOLERANCE));
        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    @Test
    public void cloudExtractionNoRemoveCollinear() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(cloudFile);
            cov = reader.read(null);
            SimpleFeatureCollection fc = process.execute(cov, null, 10d, false, null, false, true, null, null);
            iter = fc.features();

            SimpleFeature feature = iter.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            final int removeCollinearLength = referenceGeometry.getGeometryN(0).getCoordinates().length;
            assertEquals(133, removeCollinearLength);

            // The computed polygon should have more vertices due to collinear point not being removed
            final int length = geometry.getGeometryN(0).getCoordinates().length;
            assertTrue(length > removeCollinearLength);
            assertFalse(referenceGeometry.equalsExact(geometry, TOLERANCE));
        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    @Test
    public void cloudExtractionWithoutDarkPixels() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(cloudFile);
            cov = reader.read(null);

            // Exclude pixels with luminance less than 20.
            final int referenceLuminance = 10;
            List<Range<Integer>> exclusionRanges = Collections
                    .singletonList(new Range<Integer>(Integer.class, 0, referenceLuminance));
            SimpleFeatureCollection fc = process.execute(cov, exclusionRanges, 10d, false, null, true, true, null,
                    null);
            iter = fc.features();

            SimpleFeature feature = iter.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();

            Raster raster = cov.getRenderedImage().getData();
            int[] darkPixel = new int[3];

            // These positions identify a couple of dark pixels of the cloud edge
            raster.getPixel(9, 13, darkPixel);
            double luminance = ImageUtilities.RGB_TO_GRAY_MATRIX[0][0] * darkPixel[0]
                    + ImageUtilities.RGB_TO_GRAY_MATRIX[0][1] * darkPixel[1]
                    + ImageUtilities.RGB_TO_GRAY_MATRIX[0][2] * darkPixel[2];
            assertTrue(luminance < referenceLuminance);

            raster.getPixel(15, 7, darkPixel);
            luminance = ImageUtilities.RGB_TO_GRAY_MATRIX[0][0] * darkPixel[0]
                    + ImageUtilities.RGB_TO_GRAY_MATRIX[0][1] * darkPixel[1]
                    + ImageUtilities.RGB_TO_GRAY_MATRIX[0][2] * darkPixel[2];
            assertTrue(luminance < referenceLuminance);

            // The computed polygon should have different shape due to dark pixels being excluded
            assertFalse(referenceGeometry.equalsExact(geometry, TOLERANCE));
        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    @Test
    public void islandPolygonExtractionWithoutDarkPixelsAndWhiteClouds() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(islandFile);
            cov = reader.read(null);

            // Test removing black areas and clouds
            List<Range<Integer>> exclusionRanges = new ArrayList<Range<Integer>>();
            exclusionRanges.add(new Range<Integer>(Integer.class, 0, 10));
            exclusionRanges.add(new Range<Integer>(Integer.class, 253, 255));
            SimpleFeatureCollection fc = process.execute(cov, exclusionRanges, 10d, false, null, true, true, null,
                    null);
            iter = fc.features();

            SimpleFeature feature = iter.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            Geometry islandWkt = wktRead(TestData.file(this, "island.wkt"));
            assertTrue(islandWkt.equalsExact(geometry, TOLERANCE));

        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    @Test
    public void valuesEqualityTest() throws Exception {
        double p1 = 0.2;
        double p2 = 2d / 10;
        double p3 = 0.21;
        assertTrue(MarchingSquaresVectorizer.areEqual(p1, p2));
        assertFalse(MarchingSquaresVectorizer.areEqual(p1, p3));
    }

    @Test
    public void cloudExtractionWriteToDisk() throws Exception {
        GeoTiffReader reader = null;
        FeatureIterator<SimpleFeature> iter = null;
        GridCoverage2D cov = null;
        try {
            reader = new GeoTiffReader(cloudFile);
            cov = reader.read(null);
            SimpleFeatureCollection fc = process.execute(cov, null, 10d, false, null, true, true, null, null);
            assertEquals(1, fc.size());

            iter = fc.features();

            SimpleFeature feature = iter.next();
            Geometry geometry = (Geometry) feature.getDefaultGeometry();
            assertTrue(referenceGeometry.equalsExact(geometry, TOLERANCE));
            final File wktFile = TestData.temp(this, "cloudWkt.wkt");
            final File wkbFile = TestData.temp(this, "cloudWkb.wkb");
            final File shapeFile = TestData.temp(this, "cloudShape.shp");
            CoordinateReferenceSystem crs = reader.getCoordinateReferenceSystem();

            // Write geometries
            writeGeometry(WritingFormat.WKT, geometry, wktFile, crs);
            writeGeometry(WritingFormat.WKB, geometry, wkbFile, crs);
            writeGeometry(WritingFormat.SHAPEFILE, geometry, shapeFile, crs);

            // read geometries back
            assertTrue(referenceGeometry.equalsExact(wktRead(wktFile), TOLERANCE));
            assertTrue(referenceGeometry.equalsExact(wkbRead(wkbFile), TOLERANCE));

        } finally {
            if (iter != null) {
                iter.close();
            }
            if (reader != null) {
                try {
                    reader.dispose();
                } catch (Throwable t) {

                }
            }
            if (cov != null) {
                try {
                    cov.dispose(true);
                } catch (Throwable t) {

                }
            }
        }
    }

    public static void writeGeometry(final WritingFormat writingFormat, final Geometry geometry,
            final File outputFile, final CoordinateReferenceSystem crs) throws IOException {
        Utilities.ensureNonNull("writingFormat", writingFormat);
        Utilities.ensureNonNull("geometry", geometry);
        if ((outputFile != null) && outputFile.exists()) {
            FileUtils.deleteQuietly(outputFile);
        }

        writingFormat.write(geometry, outputFile, crs);

    }

}