I got it decompile org.geotools.process.vector.PointStackerProcess from gt-process-feature-x.x.jar in webapps\geoserver\WEB-INF\lib and editing in the next way:
package org.geotools.process.vector;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.process.ProcessException;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequenceFactory;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.ProgressListener;
@DescribeProcess(title = "Point Stacker", description = "Aggregates a collection of points over a grid into one point per grid cell.")
public class PointStackerProcess implements VectorProcess {
public static final String ATTR_GEOM = "geom";
public static final String ATTR_COUNT = "count";
public static final String ATTR_COUNT_UNIQUE = "countunique";
public static final String ATTR_BOUNDING_BOX_GEOM = "geomBBOX";
public static final String ATTR_BOUNDING_BOX = "envBBOX";
public static final String ATTR_NORM_COUNT = "normCount";
public static final String ATTR_NORM_COUNT_UNIQUE = "normCountUnique";
public enum PreserveLocation {
Single, Superimposed, Never;
}
@DescribeResult(name = "result", description = "Aggregated feature collection")
public SimpleFeatureCollection execute(@DescribeParameter(name = "data", description = "Input feature collection") SimpleFeatureCollection data, @DescribeParameter(name = "cellSize", description = "Grid cell size to aggregate to, in pixels") Integer cellSize, @DescribeParameter(name = "weightClusterPosition", description = "Weight cluster position based on points added", defaultValue = "false") Boolean argWeightClusterPosition, @DescribeParameter(name = "normalize", description = "Indicates whether to add fields normalized to the range 0-1.", defaultValue = "false") Boolean argNormalize, @DescribeParameter(name = "preserveLocation", description = "Indicates wheter to preserve the original location of points for single/superimposed points", defaultValue = "Never", min = 0) PreserveLocation preserveLocation, @DescribeParameter(name = "outputBBOX", description = "Bounding box for target image extent") ReferencedEnvelope outputEnv, @DescribeParameter(name = "outputWidth", description = "Target image width in pixels", minValue = 1.0D) Integer outputWidth, @DescribeParameter(name = "outputHeight", description = "Target image height in pixels", minValue = 1.0D) Integer outputHeight, ProgressListener monitor) throws ProcessException, TransformException {
CoordinateReferenceSystem srcCRS = ((SimpleFeatureType) data.getSchema()).getCoordinateReferenceSystem();
CoordinateReferenceSystem dstCRS = outputEnv.getCoordinateReferenceSystem();
MathTransform crsTransform = null;
MathTransform invTransform = null;
try {
crsTransform = CRS.findMathTransform(srcCRS, dstCRS);
invTransform = crsTransform.inverse();
} catch (FactoryException e) {
throw new ProcessException(e);
}
boolean normalize = false;
if (argNormalize != null) {
normalize = argNormalize.booleanValue();
}
boolean weightClusterPosition = false;
if (argWeightClusterPosition != null) {
weightClusterPosition = argWeightClusterPosition.booleanValue();
}
double cellSizeSrc = cellSize.intValue() * outputEnv.getWidth() / outputWidth.intValue();
Collection<StackedPoint> stackedPts = stackPoints(data, crsTransform, cellSizeSrc, weightClusterPosition, outputEnv
.getMinX(), outputEnv
.getMinY());
SimpleFeatureType schema = null;
for (StackedPoint sp : stackedPts) {
schema = createType(srcCRS, normalize, sp.getFeature());
break;
}
ListFeatureCollection result = new ListFeatureCollection(schema);
SimpleFeatureBuilder fb = new SimpleFeatureBuilder(schema);
GeometryFactory factory = new GeometryFactory((CoordinateSequenceFactory) new PackedCoordinateSequenceFactory());
double[] srcPt = new double[2];
double[] srcPt2 = new double[2];
double[] dstPt = new double[2];
double[] dstPt2 = new double[2];
int maxCount = 0;
int maxCountUnique = 0;
if (normalize) {
for (StackedPoint sp : stackedPts) {
if (maxCount < sp.getCount()) {
maxCount = sp.getCount();
}
if (maxCountUnique < sp.getCount()) {
maxCountUnique = sp.getCountUnique();
}
}
}
for (StackedPoint sp : stackedPts) {
Coordinate pt = getStackedPointLocation(preserveLocation, sp);
srcPt[0] = pt.x;
srcPt[1] = pt.y;
invTransform.transform(srcPt, 0, dstPt, 0, 1);
Coordinate psrc = new Coordinate(dstPt[0], dstPt[1]);
Point point = factory.createPoint(psrc);
fb.add(point);
fb.add(Integer.valueOf(sp.getCount()));
fb.add(Integer.valueOf(sp.getCountUnique()));
Envelope boundingBox = sp.getBoundingBox();
srcPt[0] = boundingBox.getMinX();
srcPt[1] = boundingBox.getMinY();
srcPt2[0] = boundingBox.getMaxX();
srcPt2[1] = boundingBox.getMaxY();
invTransform.transform(srcPt, 0, dstPt, 0, 1);
invTransform.transform(srcPt2, 0, dstPt2, 0, 1);
Envelope boundingBoxTransformed = new Envelope(dstPt[0], dstPt[1], dstPt2[0], dstPt2[1]);
fb.add(boundingBoxTransformed);
fb.add(boundingBoxTransformed.toString());
if (normalize) {
fb.add(Double.valueOf(sp.getCount() / maxCount));
fb.add(Double.valueOf(sp.getCountUnique() / maxCountUnique));
}
if (sp.getCount() == 1) {
//Here when count is we add the attribute value to the transformed featured
SimpleFeature ref = sp.getFeature();
for (AttributeDescriptor ad : ref.getType().getAttributeDescriptors()) {
fb.set(ad.getType().getName(), ref.getAttribute(ad.getType().getName()));
}
}
result.add(fb.buildFeature(null));
}
return (SimpleFeatureCollection) result;
}
private Coordinate getStackedPointLocation(PreserveLocation preserveLocation, StackedPoint sp) {
Coordinate pt = null;
if (PreserveLocation.Single == preserveLocation) {
if (sp.getCount() == 1) {
pt = sp.getOriginalLocation();
}
} else if (PreserveLocation.Superimposed == preserveLocation
&& sp.getCountUnique() == 1) {
pt = sp.getOriginalLocation();
}
if (pt == null) {
pt = sp.getLocation();
}
return pt;
}
private Collection<StackedPoint> stackPoints(SimpleFeatureCollection data, MathTransform crsTransform, double cellSize, boolean weightClusterPosition, double minX, double minY) throws TransformException {
SimpleFeatureIterator featureIt = data.features();
Map<Coordinate, StackedPoint> stackedPts = new HashMap<>();
double[] srcPt = new double[2];
double[] dstPt = new double[2];
Coordinate indexPt = new Coordinate();
try {
while (featureIt.hasNext()) {
SimpleFeature feature = (SimpleFeature) featureIt.next();
Geometry geom = (Geometry) feature.getDefaultGeometry();
Coordinate p = getRepresentativePoint(geom);
srcPt[0] = p.x;
srcPt[1] = p.y;
crsTransform.transform(srcPt, 0, dstPt, 0, 1);
Coordinate pout = new Coordinate(dstPt[0], dstPt[1]);
indexPt.x = pout.x;
indexPt.y = pout.y;
gridIndex(indexPt, cellSize);
StackedPoint stkPt = stackedPts.get(indexPt);
if (stkPt == null) {
double centreX = indexPt.x * cellSize + cellSize / 2.0D;
double centreY = indexPt.y * cellSize + cellSize / 2.0D;
stkPt = new StackedPoint(indexPt, new Coordinate(centreX, centreY));
stkPt.setFeature(feature);
stackedPts.put(stkPt.getKey(), stkPt);
}
stkPt.add(pout, weightClusterPosition);
}
} finally {
featureIt.close();
}
return stackedPts.values();
}
private static Coordinate getRepresentativePoint(Geometry g) {
if (g.getNumPoints() == 1) {
return g.getCoordinate();
}
return g.getCentroid().getCoordinate();
}
private void gridIndex(Coordinate griddedPt, double cellSize) {
long ix = (long) (griddedPt.x / cellSize);
long iy = (long) (griddedPt.y / cellSize);
griddedPt.x = ix;
griddedPt.y = iy;
}
private SimpleFeatureType createType(CoordinateReferenceSystem crs, boolean stretch, SimpleFeature ref) {
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
tb.add("geom", Point.class, crs);
tb.add("count", Integer.class);
tb.add("countunique", Integer.class);
tb.add("geomBBOX", Polygon.class);
tb.add("envBBOX", String.class);
if (stretch) {
tb.add("normCount", Double.class);
tb.add("normCountUnique", Double.class);
}
//Here we add the attributes from the original feature
if (ref != null) {
for (AttributeDescriptor ad : ref.getType().getAttributeDescriptors()) {
tb.add(ad);
}
}
tb.setName("stackedPoint");
SimpleFeatureType sfType = tb.buildFeatureType();
return sfType;
}
private static class StackedPoint {
//Here is where i save the last feature before transformation;
private SimpleFeature feature;
private Coordinate key;
private Coordinate centerPt;
private Coordinate location = null;
private int count = 0;
private Set<Coordinate> uniquePts;
private Envelope boundingBox = null;
public SimpleFeature getFeature() {
return feature;
}
public void setFeature(SimpleFeature feature) {
this.feature = feature;
}
public StackedPoint(Coordinate key, Coordinate centerPt) {
this.key = new Coordinate(key);
this.centerPt = centerPt;
}
public Coordinate getKey() {
return this.key;
}
public Coordinate getLocation() {
return this.location;
}
public int getCount() {
return this.count;
}
public int getCountUnique() {
if (this.uniquePts == null) {
return 1;
}
return this.uniquePts.size();
}
public Envelope getBoundingBox() {
return this.boundingBox;
}
public void add(Coordinate pt) {
add(pt, false);
}
public void add(Coordinate pt, boolean weightClusterPosition) {
this.count++;
if (this.uniquePts == null) {
this.uniquePts = new HashSet<>();
}
this.uniquePts.add(pt);
if (weightClusterPosition) {
pickWeightedLocation(pt);
} else {
pickNearestLocation(pt);
}
if (this.boundingBox == null) {
this.boundingBox = new Envelope();
} else {
this.boundingBox.expandToInclude(pt);
}
}
public Coordinate getOriginalLocation() {
if (this.uniquePts != null && this.uniquePts.size() == 1) {
return this.uniquePts.iterator().next();
}
return null;
}
private void pickWeightedLocation(Coordinate pt) {
if (this.location == null) {
this.location = pt;
return;
}
this.location = average(this.location, pt);
}
private void pickNearestLocation(Coordinate pt) {
if (this.location == null) {
this.location = average(this.centerPt, pt);
return;
}
if (pt.distance(this.centerPt) < this.location.distance(this.centerPt)) {
this.location = average(this.centerPt, pt);
}
}
private void pickCenterLocation(Coordinate pt) {
if (this.location == null) {
this.location = new Coordinate(pt);
return;
}
this.location = this.centerPt;
}
private static Coordinate average(Coordinate p1, Coordinate p2) {
double x = (p1.x + p2.x) / 2.0D;
double y = (p1.y + p2.y) / 2.0D;
return new Coordinate(x, y);
}
}
}
Then compile it again, replace in the jar file and start GeosServer