class BarnesSurfaceProcess implements VectorProcess {

    @DescribeResult(name = "result", description = "Output raster")
    public GridCoverage2D execute(
            
            // process data
            @DescribeParameter(name = "data", description = "Input features") SimpleFeatureCollection obsFeatures,
            @DescribeParameter(name = "valueAttr", description = "Name of attribute containing the data value to be interpolated") String valueAttr,
            @DescribeParameter(name = "dataLimit", description = "Limit for the number of input features processed", min=0, max=1) Integer argDataLimit,
            
            // process parameters
            @DescribeParameter(name = "scale", description = "Length scale for the interpolation, in units of the source data CRS", min=1, max=1) Double argScale,
            @DescribeParameter(name = "convergence", description = "Convergence factor for refinement (between 0 and 1, default 0.3)", min=0, max=1, defaultValue="0.3") Double argConvergence,
            @DescribeParameter(name = "passes", description = "Number of passes to compute (default = 2)", min=0, max=1) Integer argPasses,
            @DescribeParameter(name = "minObservations", description = "Minimum number of observations required to support a grid cell (default = 2)", min=0, max=1, defaultValue="2") Integer argMinObsCount,
            @DescribeParameter(name = "maxObservationDistance", description = "Maximum distance to an observation for it to support a grid cell, in units of the source CRS (default = 0, meaning all observations used)", defaultValue="0", min=0, max=1) Double argMaxObsDistance,
            @DescribeParameter(name = "noDataValue", description = "Value to use for NO_DATA cells (default = -999)", defaultValue="-999", min=0, max=1) Double argNoDataValue,
            @DescribeParameter(name = "pixelsPerCell", description = "Resolution of the computed grid in pixels per grid cell (default = 1)", defaultValue="1", min=0, max=1) Integer argPixelsPerCell,
            
            // query modification parameters
            @DescribeParameter(name = "queryBuffer", description = "Distance to expand the query envelope by, in units of the source CRS (larger values provide a more stable surface)", min=0, max=1) Double argQueryBuffer,

            // output image parameters
            @DescribeParameter(name = "outputBBOX", description = "Bounding box for output") ReferencedEnvelope outputEnv,
            @DescribeParameter(name = "outputWidth", description = "Width of the output raster in pixels") Integer outputWidth,
            @DescribeParameter(name = "outputHeight", description = "Height of the output raster in pixels") Integer outputHeight,
            
            ProgressListener monitor) throws ProcessException {

        /**---------------------------------------------
         * Check that process arguments are valid
         * ---------------------------------------------
         */
        if (valueAttr == null || valueAttr.length() <= 0) {
            throw new IllegalArgumentException("Value attribute must be specified");
        }

        /**---------------------------------------------
         * Set up required information from process arguments.
         * ---------------------------------------------
         */
        int dataLimit = 0;
        if (argDataLimit != null) dataLimit = argDataLimit;
        
        double lengthScale = argScale;
        double convergenceFactor = argConvergence != null ? argConvergence : 0.3;
        int passes = argPasses != null ? argPasses : 2;
        int minObsCount = argMinObsCount != null ? argMinObsCount : 2;
        double maxObsDistance = argMaxObsDistance != null ? argMaxObsDistance : 0.0;
        float noDataValue = (float) (argNoDataValue != null ? argNoDataValue : -999);
        int pixelsPerCell = 1;
        if (argPixelsPerCell != null && argPixelsPerCell > 1) {
            pixelsPerCell = argPixelsPerCell;
        }
        int gridWidth = outputWidth;
        int gridHeight = outputHeight;
        if (pixelsPerCell > 1) {
            gridWidth = outputWidth / pixelsPerCell;
            gridHeight = outputHeight / pixelsPerCell;
        }
        
        CoordinateReferenceSystem srcCRS = obsFeatures.getSchema().getCoordinateReferenceSystem();
        CoordinateReferenceSystem dstCRS = outputEnv.getCoordinateReferenceSystem();
        MathTransform trans = null;
        try {
            trans = CRS.findMathTransform(srcCRS, dstCRS);
        } catch (FactoryException e) {
            throw new ProcessException(e);
        }
        /**---------------------------------------------
         * Convert distance parameters to units of the destination CRS.
         * ---------------------------------------------
         */
        double distanceConversionFactor = distanceConversionFactor(srcCRS, dstCRS);
        double dstLengthScale = lengthScale * distanceConversionFactor;
        double dstMaxObsDistance = maxObsDistance * distanceConversionFactor;

        /**---------------------------------------------
         * Extract the input observation points
         * ---------------------------------------------
         */
        Coordinate[] pts = null;
        try {
            pts = extractPoints(obsFeatures, valueAttr, trans, dataLimit);
        } catch (CQLException e) {
            throw new ProcessException(e);
        }

        /**---------------------------------------------
         * Do the processing
         * ---------------------------------------------
         */
        //Stopwatch sw = new Stopwatch();
        // interpolate the surface at the specified resolution
        float[][] barnesGrid = createBarnesGrid(pts, dstLengthScale, convergenceFactor, passes, minObsCount, dstMaxObsDistance, noDataValue, outputEnv, gridWidth, gridHeight);
        
        // flip now, since grid size may be smaller
        barnesGrid = flipXY(barnesGrid);
        
        // upsample to output resolution if necessary
        float[][] outGrid = barnesGrid;
        if (pixelsPerCell > 1)
            outGrid = upsample(barnesGrid, noDataValue, outputWidth, outputHeight);
        
        // convert to the GridCoverage2D required for output
        GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(GeoTools.getDefaultHints());
        GridCoverage2D gridCov = gcf.create("values", outGrid, outputEnv);
        
        //System.out.println("**************  Barnes Surface computed in " + sw.getTimeString());
        
        return gridCov;
    }

}