PolygonExtractionProcess implements RasterProcess { @DescribeResult(name = "result", description = "The extracted polygon features") public SimpleFeatureCollection execute( @DescribeParameter(name = "data", description = "Source raster") GridCoverage2D coverage, @DescribeParameter(name = "band", description = "Source band to use (default = 0)", min = 0, defaultValue = "0") Integer band, @DescribeParameter(name = "insideEdges", description = "Indicates whether to vectorize boundaries between adjacent regions with non-outside values", min = 0) Boolean insideEdges, @DescribeParameter(name = "roi", description = "Geometry delineating the region of interest (in raster coordinate system)", min = 0) Geometry roi, @DescribeParameter(name = "nodata", description = "Value to treat as NODATA (default is 0)", collectionType = Number.class, min = 0) Collection<Number> noDataValues, @DescribeParameter(name = "ranges", description = "Specifier for a value range in the format ( START ; END ). START and END values are optional. [ and ] can also be used as brackets, to indicate inclusion of the relevant range endpoint.", collectionType = Range.class, min = 0) List<Range> classificationRanges, ProgressListener progressListener) throws ProcessException { if (coverage == null) { throw new ProcessException("Invalid input, source grid coverage should be not null"); } if (band == null) { band = 0; } else if (band < 0 || band >= coverage.getNumSampleDimensions()) { throw new ProcessException("Invalid input, invalid band number:" + band); } boolean hasClassificationRanges = classificationRanges != null && classificationRanges.size() > 0; if (hasClassificationRanges) { final RangeLookupProcess lookup = new RangeLookupProcess(); coverage = lookup.execute( coverage, band, classificationRanges, progressListener); } List<Number> outsideValues = new ArrayList<Number>(); if (noDataValues != null && !hasClassificationRanges) { outsideValues.addAll(noDataValues); } else { outsideValues.add(0); } final AffineTransform mt2D = (AffineTransform) coverage.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT); final RenderedImage raster = coverage.getRenderedImage(); ParameterBlockJAI pb = new ParameterBlockJAI("Vectorize"); pb.setSource("source0", raster); if (roi != null) { pb.setParameter("roi", CoverageUtilities.prepareROI(roi, mt2D)); } pb.setParameter("band", band); pb.setParameter("outsideValues", outsideValues); if (insideEdges != null) { pb.setParameter("insideEdges", insideEdges); } final RenderedOp dest = JAI.create("Vectorize", pb); @SuppressWarnings("unchecked") final Collection<Polygon> prop = (Collection<Polygon>) dest.getProperty(VectorizeDescriptor.VECTOR_PROPERTY_NAME); final SimpleFeatureType featureType = CoverageUtilities.createFeatureType(coverage, Polygon.class); final SimpleFeatureBuilder builder = new SimpleFeatureBuilder(featureType); int i = 0; final ListFeatureCollection featureCollection = new ListFeatureCollection(featureType); final AffineTransformation jtsTransformation = new AffineTransformation( mt2D.getScaleX(), mt2D.getShearX(), mt2D.getTranslateX(), mt2D.getShearY(), mt2D.getScaleY(), mt2D.getTranslateY()); for (Polygon polygon : prop) { Double value = (Double) polygon.getUserData(); polygon.setUserData(null); polygon.apply(jtsTransformation); builder.set("the_geom", polygon); builder.set("value", value); featureCollection.add(builder.buildFeature(String.valueOf(i++))); } return featureCollection; }  @DescribeResult(name = "result", description = "The extracted polygon features") SimpleFeatureCollection execute(
            @DescribeParameter(name = "data", description = "Source raster") GridCoverage2D coverage,
            @DescribeParameter(name = "band", description = "Source band to use (default = 0)", min = 0, defaultValue = "0") Integer band,
            @DescribeParameter(name = "insideEdges", description = "Indicates whether to vectorize boundaries between adjacent regions with non-outside values", min = 0) Boolean insideEdges,
            @DescribeParameter(name = "roi", description = "Geometry delineating the region of interest (in raster coordinate system)", min = 0) Geometry roi,
            @DescribeParameter(name = "nodata", description = "Value to treat as NODATA (default is 0)",
            collectionType = Number.class, min = 0) Collection<Number> noDataValues,
            @DescribeParameter(name = "ranges", description = "Specifier for a value range in the format ( START ; END ).  START and END values are optional. [ and ] can also be used as brackets, to indicate inclusion of the relevant range endpoint.", 
            collectionType = Range.class, min = 0) List<Range> classificationRanges,
            ProgressListener progressListener);  }
@Test public void simpleSmallCoverage() throws Exception { GridCoverage2D cov = buildSmallCoverage(); final int perimeters[] = { 4, 16, 4 }; final int areas[] = {1, 7, 1}; int band = 0; Set<Double> outsideValues = Collections.singleton(0D); SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.TRUE, null, null, null, null); assertEquals(3, fc.size()); FeatureIterator iter = fc.features(); try { while (iter.hasNext()) { SimpleFeature feature = (SimpleFeature) iter.next(); Polygon poly = (Polygon) feature.getDefaultGeometry(); int value = ((Number) feature.getAttribute("value")).intValue(); assertEquals(perimeters[value - 1], (int) (poly.getBoundary().getLength() + 0.5)); assertEquals(areas[value - 1], (int) (poly.getArea() + 0.5)); } } finally { iter.close(); } }
@Test public void checkThatHolesArePresentInPolygons() throws Exception { final float[][] DATA = { {1, 1, 1, 1, 0, 1, 1, 1, 1}, {1, 0, 0, 1, 0, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 1, 0, 0, 1}, {1, 1, 1, 1, 0, 1, 1, 1, 1} }; final int NUM_POLYS = 2; GridCoverage2D cov = covFactory.create( "coverage", DATA, new ReferencedEnvelope(0, DATA[0].length, 0, DATA.length, null)); SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.TRUE, null, null, null, null); assertEquals(NUM_POLYS, fc.size()); SimpleFeatureIterator iter = fc.features(); try { while (iter.hasNext()) { Polygon poly = (Polygon) iter.next().getDefaultGeometry(); assertEquals(1, poly.getNumInteriorRing()); } } finally { iter.close(); } }
@Test public void treatZeroAsDataValue() throws Exception { final float[][] DATA = { {1, 1, 1, 1, 0, 1, 1, 1, 1}, {1, 0, 0, 1, 0, 1, 0, 0, 1}, {1, 0, 0, 1, 0, 1, 0, 0, 1}, {1, 1, 1, 1, 0, 1, 1, 1, 1} }; final int NUM_POLYS = 5; GridCoverage2D cov = covFactory.create( "coverage", DATA, new ReferencedEnvelope(0, DATA[0].length, 0, DATA.length, null)); Number[] noDataValues = { -1 }; SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.TRUE, null, Arrays.asList(noDataValues), null, null); assertEquals(NUM_POLYS, fc.size()); }
@Test public void ignoreInteriorBoundariesBetweenRegions() throws Exception { final float[][] DATA = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 2, 2, 2, 3, 3, 0}, {0, 1, 1, 1, 2, 2, 2, 3, 3, 0}, {0, 1, 1, 1, 2, 2, 2, 3, 3, 0}, {0, 4, 4, 5, 5, 5, 6, 6, 6, 0}, {0, 4, 4, 5, 5, 5, 6, 6, 6, 0}, {0, 4, 4, 5, 5, 5, 6, 6, 6, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; final int width = DATA[0].length; final int height = DATA.length; GridCoverage2D cov = covFactory.create( "coverage", DATA, new ReferencedEnvelope(0, width, 0, height, null)); SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.FALSE, null, null, null, null); assertEquals(1, fc.size()); Geometry geom = (Geometry) fc.features().next().getDefaultGeometry(); Envelope env = geom.getEnvelopeInternal(); assertEquals(new Envelope(1, width-1, 1, height-1), env); }
@Test public void singleLShapedRegion() throws Exception { final float[][] DATA = { {0, 0, 0, 0, 0}, {0, 1, 0, 0, 0}, {0, 1, 1, 1, 0}, {0, 0, 0, 0, 0} }; GridCoverage2D cov = covFactory.create( "coverage", DATA, new ReferencedEnvelope(0, DATA[0].length, 0, DATA.length, null)); Set<Double> outsideValues = Collections.singleton(0D); SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.TRUE, null, null, null, null); assertEquals(1, fc.size()); SimpleFeature feature = fc.features().next(); int value = ((Number) feature.getAttribute("value")).intValue(); assertEquals(1, value); }
@Test public void extractPolygonsFromViewshedRaster() throws Exception { final double ROUND_OFF_TOLERANCE = 1.0e-4D; URL url = getClass().getResource("viewshed.tif"); BufferedImage img = ImageIO.read(url); Rectangle bounds = new Rectangle(img.getMinX(), img.getMinY(), img.getWidth(), img.getHeight()); ReferencedEnvelope env = new ReferencedEnvelope(bounds, null); GridCoverage2D cov = covFactory.create("coverage", img, env); final int OUTSIDE = -1; List<Number> noDataValues = new ArrayList<Number>(); noDataValues.add(OUTSIDE); SimpleFeatureCollection fc = process.execute( cov, 0, Boolean.TRUE, null, noDataValues, null, null); SimpleFeatureIterator iter = fc.features(); Map<Integer, Double> areas = new HashMap<Integer, Double>(); try { while (iter.hasNext()) { SimpleFeature feature = iter.next(); Geometry geom = (Geometry) feature.getDefaultGeometry(); assertTrue(geom.isValid()); int value = ((Number) feature.getAttribute("value")).intValue(); if (value != OUTSIDE) { Double sum = areas.get(value); if (sum == null) { sum = 0.0d; } sum += geom.getArea(); areas.put(value, sum); } } } finally { iter.close(); } Map<Integer, Double> imgAreas = new HashMap<Integer, Double>(); Raster tile = img.getTile(0, 0); for (int y = img.getMinY(), ny = 0; ny < img.getHeight(); y++, ny++) { for (int x = img.getMinX(), nx = 0; nx < img.getWidth(); x++, nx++) { double gridvalue = tile.getSampleDouble(x, y, 0); if (Math.abs(gridvalue - OUTSIDE) < TOL) { Double sum = areas.get((int)gridvalue); if (sum == null) { sum = 1.0D; } else { sum += 1.0D; } areas.put((int)gridvalue, sum); } } } for (Integer i : imgAreas.keySet()) { double ratio = areas.get(i) / imgAreas.get(i); assertTrue(Math.abs(1.0D - ratio) < ROUND_OFF_TOLERANCE); } }
@Test public void classificationRanges() { final float[][] DATA = { {1, 1, 3, 3, 5, 5, 7, 7}, {1, 1, 3, 3, 5, 5, 7, 7}, {2, 2, 4, 4, 6, 6, 8, 8}, {2, 2, 4, 4, 6, 6, 8, 8}, {9, 9, 9, 9, 9, 9, 9, 9} }; final int width = DATA[0].length; final int height = DATA.length; GridCoverage2D cov = covFactory.create( "coverage", DATA, new ReferencedEnvelope(0, width, 0, height, null)); List<Range> classificationRanges = new ArrayList<Range>(); Range<Integer> r1 = Range.create(1, true, 4, true); Range<Integer> r2 = Range.create(5, true, 8, true); classificationRanges.add(r1); classificationRanges.add(r2); SimpleFeatureCollection fc = process.execute( cov, 0, Boolean.TRUE, null, null, classificationRanges, null); assertEquals(2, fc.size()); SimpleFeatureIterator iter = fc.features(); List<Integer> expectedValues = new ArrayList<Integer>(); expectedValues.add(1); expectedValues.add(2); try { while (iter.hasNext()) { SimpleFeature feature = iter.next(); Integer value = ((Number) feature.getAttribute("value")).intValue(); assertTrue(expectedValues.remove(value)); Polygon poly = (Polygon) feature.getDefaultGeometry(); assertEquals(16.0, poly.getArea(), TOL); } } finally { iter.close(); } }
@Ignore("See GEOT-3861") @Test public void useCoverageBoundsAsROI() { final float[][] DATA = { {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1} }; final int width = DATA[0].length; final int height = DATA.length; final double cellSize = 1000; final double minX = 10000; final double minY = 5000; final double maxX = minX + width * cellSize; final double maxY = minY + height * cellSize; final ReferencedEnvelope dataEnv = new ReferencedEnvelope(minX, maxX, minY, maxY, null); GridCoverage2D cov = covFactory.create("coverage", DATA, dataEnv); Polygon roiGeom = JTS.toGeometry(dataEnv); SimpleFeatureCollection fc = process.execute(cov, 0, Boolean.TRUE, roiGeom, null, null, null); assertEquals(1, fc.size()); SimpleFeature feature = fc.features().next(); assertEquals(1, ((Number) feature.getAttribute("value")).intValue()); ReferencedEnvelope polyEnv = JTS.toEnvelope((Geometry) feature.getDefaultGeometry()); assertTrue("Expected " + dataEnv + " but got " + polyEnv, dataEnv.boundsEquals2D(polyEnv, TOL)); }
@Ignore("See GEOT-3861") @Test public void useROIToExcludeLeftAndRightImageCols() { final float[][] DATA = { {0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 2, 2, 2, 2, 3, 3}, {1, 1, 2, 2, 2, 2, 3, 3}, {1, 1, 2, 2, 2, 2, 3, 3}, {1, 1, 2, 2, 2, 2, 3, 3}, {1, 1, 2, 2, 2, 2, 3, 3}, {0, 0, 0, 0, 0, 0, 0, 0} }; final int width = DATA[0].length; final int height = DATA.length; final double cellSize = 1000; final double minX = 10000; final double minY = 5000; final double maxX = minX + width * cellSize; final double maxY = minY + height * cellSize; final ReferencedEnvelope dataEnv = new ReferencedEnvelope(minX, maxX, minY, maxY, null); GridCoverage2D cov = covFactory.create("coverage", DATA, dataEnv); ReferencedEnvelope processEnv = new ReferencedEnvelope( minX + cellSize, maxX - cellSize, minY, maxY, null); Polygon roiGeometry = JTS.toGeometry(processEnv); SimpleFeatureCollection fc = process.execute( cov, 0, Boolean.TRUE, roiGeometry, null, null, null); assertEquals(3, fc.size()); final double cellArea = cellSize * cellSize; final double[] areas = { 5 * cellArea, 20 * cellArea, 5 * cellArea }; List<Integer> expectedValues = new ArrayList<Integer>(); expectedValues.addAll(Arrays.asList(1, 2, 3)); SimpleFeatureIterator iter = fc.features(); try { while (iter.hasNext()) { SimpleFeature feature = iter.next(); Integer value = ((Number) feature.getAttribute("value")).intValue(); System.out.println(value); assertTrue(expectedValues.remove(value)); Polygon poly = (Polygon) feature.getDefaultGeometry(); System.out.println(poly.toText()); assertEquals(areas[value - 1], poly.getArea(), TOL); } } finally { iter.close(); } }
@Test public void testCallViaProcessFactory() { Process process = Processors.createProcess(new NameImpl("ras", "PolygonExtraction")); assertNotNull(process); Map<String, Object> inputs = new HashMap<String, Object>(); inputs.put("data", buildSmallCoverage()); inputs.put("ranges", Arrays.asList(new Range<Integer>(0, true, 1, true), new Range<Integer>(2, true, 3, true))); Map<String, Object> results = process.execute(inputs, null); Object result = results.get("result"); assertTrue(result instanceof SimpleFeatureCollection); SimpleFeatureCollection fc = (SimpleFeatureCollection) result; assertEquals(4, fc.size()); }