class 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 {


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

        // do we have classification ranges?
        boolean hasClassificationRanges = classificationRanges != null && classificationRanges.size() > 0;

        // apply the classification by setting 0 as the default value and using 1, ..., numClasses for the other classes.
        // we use 0 also as the noData for the resulting coverage.
        if (hasClassificationRanges) {

            final RangeLookupProcess lookup = new RangeLookupProcess();
            coverage = lookup.execute(
                    coverage,
                    band,
                    classificationRanges,
                    progressListener);
        }

        // Use noDataValues to set the "outsideValues" parameter of the Vectorize
        // operation unless classificationRanges are in use, in which case the
        // noDataValues arg is ignored.
        List<Number> outsideValues = new ArrayList<Number>();
        if (noDataValues != null && !hasClassificationRanges) {
            outsideValues.addAll(noDataValues);
        } else {
            outsideValues.add(0);
        }
        
        //
        // GRID TO WORLD preparation
        //
        final AffineTransform mt2D = (AffineTransform) coverage.getGridGeometry().getGridToCRS2D(PixelOrientation.UPPER_LEFT);

        // get the rendered image
        final RenderedImage raster = coverage.getRenderedImage();

        // perform jai operation
        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);
        }
        // pb.setParameter("removeCollinear", false);  

        final RenderedOp dest = JAI.create("Vectorize", pb);
        @SuppressWarnings("unchecked")
        final Collection<Polygon> prop = (Collection<Polygon>) dest.getProperty(VectorizeDescriptor.VECTOR_PROPERTY_NAME);

        // wrap as a feature collection and return
        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) {
            // get value
            Double value = (Double) polygon.getUserData();
            polygon.setUserData(null);
            // filter coordinates in place
            polygon.apply(jtsTransformation);

            // create feature and add to list
            builder.set("the_geom", polygon);
            builder.set("value", value);

            featureCollection.add(builder.buildFeature(String.valueOf(i++)));

        }

        //return value
        return featureCollection;
    }

}