class HeatmapProcess implements VectorProcess {

    @DescribeResult(name = "result", description = "Output raster")
    public GridCoverage2D execute(

            // process data
            @DescribeParameter(name = "data", description = "Input features") SimpleFeatureCollection obsFeatures,

            // process parameters
            @DescribeParameter(name = "radiusPixels", description = "Radius of the density kernel in pixels") Integer argRadiusPixels,
            @DescribeParameter(name = "weightAttr", description = "Name of the attribute to use for data point weight", min = 0, max = 1) String valueAttr,
            @DescribeParameter(name = "pixelsPerCell", description = "Resolution at which to compute the heatmap (in pixels). Default = 1", defaultValue="1", min = 0, max = 1) Integer argPixelsPerCell,

            // output image parameters
            @DescribeParameter(name = "outputBBOX", description = "Bounding box of the output") ReferencedEnvelope argOutputEnv,
            @DescribeParameter(name = "outputWidth", description = "Width of output raster in pixels") Integer argOutputWidth,
            @DescribeParameter(name = "outputHeight", description = "Height of output raster in pixels") Integer argOutputHeight,

            ProgressListener monitor) throws ProcessException {

        /**
         * -------- Extract required information from process arguments -------------
         */
        int pixelsPerCell = 1;
        if (argPixelsPerCell != null && argPixelsPerCell > 1) {
            pixelsPerCell = argPixelsPerCell;
        }
        int outputWidth = argOutputWidth;
        int outputHeight = argOutputHeight;
        int gridWidth = outputWidth;
        int gridHeight = outputHeight;
        if (pixelsPerCell > 1) {
            gridWidth = outputWidth / pixelsPerCell;
            gridHeight = outputHeight / pixelsPerCell;
        }

        /**
         * Compute transform to convert input coords into output CRS
         */
        CoordinateReferenceSystem srcCRS = obsFeatures.getSchema().getCoordinateReferenceSystem();
        CoordinateReferenceSystem dstCRS = argOutputEnv.getCoordinateReferenceSystem();
        MathTransform trans = null;
        try {
            trans = CRS.findMathTransform(srcCRS, dstCRS);
        } catch (FactoryException e) {
            throw new ProcessException(e);
        }

        //------------ Kernel Radius
        /*
         * // not used for now - only pixel radius values are supported double
         * distanceConversionFactor = distanceConversionFactor(srcCRS, dstCRS); double dstRadius =
         * argRadius * distanceConversionFactor;
         */
        int radiusCells = 100;
        if (argRadiusPixels != null)
            radiusCells = argRadiusPixels;
        if (pixelsPerCell > 1) {
            radiusCells /= pixelsPerCell;
        }


        /**
         * -------------- Extract the input observation points -----------
         */
        HeatmapSurface heatMap = new HeatmapSurface(radiusCells, argOutputEnv, gridWidth,
                gridHeight);
        try {
            extractPoints(obsFeatures, valueAttr, trans, heatMap);
        } catch (CQLException e) {
            throw new ProcessException(e);
        }

        /**
         * --------------- Do the processing ------------------------------
         */
        // Stopwatch sw = new Stopwatch();
        // compute the heatmap at the specified resolution
        float[][] heatMapGrid = heatMap.computeSurface();

        // flip now, since grid size may be smaller
        heatMapGrid = flipXY(heatMapGrid);

        // upsample to output resolution if necessary
        float[][] outGrid = heatMapGrid;
        if (pixelsPerCell > 1)
            outGrid = upsample(heatMapGrid, -999, outputWidth, outputHeight);

        // convert to the GridCoverage2D required for output
        GridCoverageFactory gcf = CoverageFactoryFinder.getGridCoverageFactory(GeoTools.getDefaultHints());
        GridCoverage2D gridCov = gcf.create("Process Results", outGrid, argOutputEnv);

        // System.out.println("**************  Heatmap computed in " + sw.getTimeString());

        return gridCov;
    }

}