diff --git a/jt-classbreaks/pom.xml b/jt-classbreaks/pom.xml
new file mode 100644
index 00000000..6a312bfd
--- /dev/null
+++ b/jt-classbreaks/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ it.geosolutions.jaiext
+ jaiext
+ 1.1-SNAPSHOT
+
+ it.geosolutions.jaiext.classbreaks
+ jt-classbreaks
+ 1.1-SNAPSHOT
+ jt-classbreaks
+ http://maven.apache.org
+
+ UTF-8
+
+
+
+ it.geosolutions.jaiext.utilities
+ jt-utilities
+ ${project.version}
+
+
+ it.geosolutions.jaiext.utilities
+ jt-utilities
+ ${project.version}
+ test-jar
+ test
+
+
+
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksDescriptor.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksDescriptor.java
new file mode 100644
index 00000000..09e4e578
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksDescriptor.java
@@ -0,0 +1,177 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.image.RenderedImage;
+
+import javax.media.jai.OperationDescriptorImpl;
+import javax.media.jai.ROI;
+import javax.media.jai.registry.RenderedRegistryMode;
+
+/** Operation descriptor for the ClassBreaks operation. */
+public class ClassBreaksDescriptor extends OperationDescriptorImpl {
+
+ public static final String CLASSIFICATION_PROPERTY = "Classification";
+
+ public static final String NAME = "ClassBreaks";
+
+ static final int NUM_CLASSES_ARG = 0;
+ static final int METHOD_ARG = 1;
+ static final int EXTREMA_ARG = 2;
+ static final int ROI_ARG = 3;
+ static final int BAND_ARG = 4;
+ static final int X_PERIOD_ARG = 5;
+ static final int Y_PERIOD_ARG = 6;
+ static final int NODATA_ARG = 7;
+ static final int HISTOGRAM_ARG = 8;
+ static final int HISTOGRAM_BINS = 9;
+
+ static String[] paramNames =
+ new String[] {
+ "numClasses",
+ "method",
+ "extrema",
+ "roi",
+ "band",
+ "xPeriod",
+ "yPeriod",
+ "noData",
+ "histogram",
+ "histogramBins"
+ };
+
+ static final Class>[] paramClasses = {
+ Integer.class,
+ ClassificationMethod.class,
+ Double[][].class,
+ ROI.class,
+ Integer[].class,
+ Integer.class,
+ Integer.class,
+ Double.class,
+ Boolean.class,
+ Integer.class
+ };
+
+ static final Object[] paramDefaults = {
+ 10,
+ ClassificationMethod.EQUAL_INTERVAL,
+ null,
+ (ROI) null,
+ new Integer[] {Integer.valueOf(0)},
+ 1,
+ 1,
+ null,
+ false,
+ 256
+ };
+
+ public ClassBreaksDescriptor() {
+ super(
+ new String[][] {
+ {"GlobalName", NAME},
+ {"LocalName", NAME},
+ {"Vendor", "it.geosolutions.jaiext"},
+ {
+ "Description",
+ "Classifies image values using equal interval method and calculates "
+ + "statistics for each class"
+ },
+ {"DocURL", ""},
+ {"Version", "1.0"},
+ {
+ String.format("arg%dDesc", NUM_CLASSES_ARG),
+ String.format("%s - number of classes or bins", paramNames[NUM_CLASSES_ARG])
+ },
+ {
+ String.format("arg%dDesc", METHOD_ARG),
+ String.format("%s - classification method", paramNames[METHOD_ARG])
+ },
+ {
+ String.format("arg%dDesc", EXTREMA_ARG),
+ String.format("%s - range of values to include", paramNames[EXTREMA_ARG])
+ },
+ {
+ String.format("arg%dDesc", ROI_ARG),
+ String.format(
+ "%s (default %s) - region-of-interest constrainting the values to be counted",
+ paramNames[ROI_ARG], paramDefaults[ROI_ARG])
+ },
+ {
+ String.format("arg%dDesc", BAND_ARG),
+ String.format(
+ "%s (default %s) - bands of the image to process",
+ paramNames[BAND_ARG], paramDefaults[BAND_ARG])
+ },
+ {
+ String.format("arg%dDesc", X_PERIOD_ARG),
+ String.format(
+ "%s (default %s) - horizontal sampling rate",
+ paramNames[X_PERIOD_ARG], paramDefaults[X_PERIOD_ARG])
+ },
+ {
+ String.format("arg%dDesc", Y_PERIOD_ARG),
+ String.format(
+ "%s (default %s) - vertical sampling rate",
+ paramNames[Y_PERIOD_ARG], paramDefaults[Y_PERIOD_ARG])
+ },
+ {
+ String.format("arg%dDesc", NODATA_ARG),
+ String.format(
+ "%s (default %s) - value to treat as NODATA",
+ paramNames[NODATA_ARG], paramDefaults[NODATA_ARG])
+ },
+ {
+ String.format("arg%dDesc", HISTOGRAM_ARG),
+ String.format(
+ "%s (default %s) - if true, a histogram based computation will be used",
+ paramNames[HISTOGRAM_ARG], paramDefaults[HISTOGRAM_ARG])
+ },
+ {
+ String.format("arg%dDesc", HISTOGRAM_BINS),
+ String.format(
+ "%s (default %s) - if true, number of histogram bins to be used",
+ paramNames[HISTOGRAM_BINS], paramDefaults[HISTOGRAM_BINS])
+ },
+ },
+ new String[] {RenderedRegistryMode.MODE_NAME},
+ new String[] {"source0"},
+ new Class>[][] {{RenderedImage.class}},
+ paramNames,
+ paramClasses,
+ paramDefaults,
+ null // valid values (none defined)
+ );
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImage.java
new file mode 100644
index 00000000..41572b08
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImage.java
@@ -0,0 +1,241 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.*;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+import javax.media.jai.PixelAccessor;
+import javax.media.jai.ROI;
+import javax.media.jai.StatisticsOpImage;
+import javax.media.jai.UnpackedImageData;
+
+/** Abstract base class for various operations corresponding to classification method. */
+public abstract class ClassBreaksOpImage extends StatisticsOpImage {
+
+ /* number of classes */
+ protected Integer numClasses;
+
+ /* range of values to calculate per band */
+ protected Double[][] extrema;
+
+ /* bands to process */
+ protected Integer[] bands;
+
+ /* no data value */
+ protected Double noData;
+
+ public ClassBreaksOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData) {
+
+ super(image, roi, xStart, yStart, xPeriod, yPeriod);
+
+ this.numClasses = numClasses;
+ this.extrema = extrema;
+ this.bands = bands;
+ this.noData = noData;
+ }
+
+ @Override
+ protected String[] getStatisticsNames() {
+ return new String[] {ClassBreaksDescriptor.CLASSIFICATION_PROPERTY};
+ }
+
+ @Override
+ public Object getProperty(String name) {
+ Object obj = properties.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ if (obj == Image.UndefinedProperty) {
+ // not calculated yet, give subclass a chance to optimize in cases where enough
+ // parameters are specified that the image does not have to be scanned
+ Classification c = preCalculate();
+ if (c != null) {
+ properties.setProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY, c);
+ }
+ }
+
+ return super.getProperty(name);
+ }
+
+ @Override
+ public void setProperty(String name, Object value) {
+ if (value instanceof Classification) {
+ // calculation over, calculate the breaks
+ Classification c = (Classification) value;
+ for (int b = 0; b < bands.length; b++) {
+ postCalculate(c, b);
+ }
+ }
+
+ super.setProperty(name, value);
+ }
+
+ @Override
+ protected Object createStatistics(String name) {
+ if (ClassBreaksDescriptor.CLASSIFICATION_PROPERTY.equals(name)) {
+ return createClassification();
+ }
+ return Image.UndefinedProperty;
+ }
+
+ @Override
+ protected void accumulateStatistics(String name, Raster raster, Object obj) {
+ if (!ClassBreaksDescriptor.CLASSIFICATION_PROPERTY.equals(name)) {
+ return;
+ }
+
+ Classification c = (Classification) obj;
+
+ // ClassifiedStats2 stats = (ClassifiedStats2) obj;
+ SampleModel sampleModel = raster.getSampleModel();
+
+ Rectangle bounds = raster.getBounds();
+
+ LinkedList rectList;
+ if (roi == null) { // ROI is the whole Raster
+ rectList = new LinkedList();
+ rectList.addLast(bounds);
+ } else {
+ rectList =
+ roi.getAsRectangleList(
+ bounds.x, bounds.y,
+ bounds.width, bounds.height);
+ if (rectList == null) {
+ return; // ROI does not intersect with Raster boundary.
+ }
+ }
+
+ PixelAccessor accessor = new PixelAccessor(sampleModel, null);
+
+ ListIterator iterator = rectList.listIterator(0);
+
+ while (iterator.hasNext()) {
+ Rectangle r = (Rectangle) iterator.next();
+ int tx = r.x;
+ int ty = r.y;
+
+ // Find the actual ROI based on start and period.
+ r.x = startPosition(tx, xStart, xPeriod);
+ r.y = startPosition(ty, yStart, yPeriod);
+ r.width = tx + r.width - r.x;
+ r.height = ty + r.height - r.y;
+
+ if (r.width <= 0 || r.height <= 0) {
+ continue; // no pixel to count in this rectangle
+ }
+
+ switch (accessor.sampleType) {
+ case PixelAccessor.TYPE_BIT:
+ case DataBuffer.TYPE_BYTE:
+ case DataBuffer.TYPE_USHORT:
+ case DataBuffer.TYPE_SHORT:
+ case DataBuffer.TYPE_INT:
+ // countPixelsInt(accessor, raster, r, xPeriod, yPeriod, breaks);
+ // break;
+ case DataBuffer.TYPE_FLOAT:
+ case DataBuffer.TYPE_DOUBLE:
+ default:
+ calculate(accessor, raster, r, xPeriod, yPeriod, c);
+ break;
+ }
+ }
+ }
+
+ void calculate(
+ PixelAccessor accessor,
+ Raster raster,
+ Rectangle rect,
+ int xPeriod,
+ int yPeriod,
+ Classification c) {
+ UnpackedImageData uid = accessor.getPixels(raster, rect, DataBuffer.TYPE_DOUBLE, false);
+
+ double[][] doubleData = uid.getDoubleData();
+ int pixelStride = uid.pixelStride * xPeriod;
+ int lineStride = uid.lineStride * yPeriod;
+ int[] offsets = uid.bandOffsets;
+
+ for (int i = 0; i < bands.length; i++) {
+ int b = bands[i];
+
+ double[] data = doubleData[b];
+ int lineOffset = offsets[b]; // line offset
+
+ for (int h = 0; h < rect.height; h += yPeriod) {
+ int pixelOffset = lineOffset; // pixel offset
+ lineOffset += lineStride;
+
+ for (int w = 0; w < rect.width; w += xPeriod) {
+ double d = data[pixelOffset];
+ pixelOffset += pixelStride;
+
+ // skip no data
+ if (noData != null && noData.equals(d)) {
+ continue;
+ }
+
+ handleValue(d, c, i);
+ }
+ }
+ }
+ }
+
+ protected abstract void handleValue(double d, Classification c, int band);
+
+ protected abstract Classification createClassification();
+
+ protected Classification preCalculate() {
+ return null;
+ }
+
+ protected abstract void postCalculate(Classification c, int band);
+
+ /** Finds the first pixel at or after pos
to be counted. */
+ private int startPosition(int pos, int start, int Period) {
+ int t = (pos - start) % Period;
+ return t == 0 ? pos : pos + (Period - t);
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksRIF.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksRIF.java
new file mode 100644
index 00000000..b653579f
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassBreaksRIF.java
@@ -0,0 +1,139 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.BAND_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.EXTREMA_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.HISTOGRAM_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.HISTOGRAM_BINS;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.METHOD_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.NODATA_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.NUM_CLASSES_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.ROI_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.X_PERIOD_ARG;
+import static it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor.Y_PERIOD_ARG;
+
+import java.awt.*;
+import java.awt.image.RenderedImage;
+import java.awt.image.renderable.ParameterBlock;
+
+import javax.media.jai.CRIFImpl;
+import javax.media.jai.ROI;
+
+/**
+ * RIF for the ClassBreaks operation.
+ *
+ *
This factory ends up creating on of the following operations based on the "method" parameter.
+ *
+ *
+ * - {@link EqualIntervalBreaksOpImage}
+ *
- {@link QuantileBreaksOpImage}
+ *
- {@link NaturalBreaksOpImage}
+ *
+ */
+public class ClassBreaksRIF extends CRIFImpl {
+
+ public ClassBreaksRIF() {
+ super("ClassBreaks");
+ }
+
+ public RenderedImage create(ParameterBlock pb, RenderingHints hints) {
+ RenderedImage src = pb.getRenderedSource(0);
+
+ int xStart = src.getMinX(); // default values
+ int yStart = src.getMinY();
+
+ Integer numBins = pb.getIntParameter(NUM_CLASSES_ARG);
+ ClassificationMethod method = (ClassificationMethod) pb.getObjectParameter(METHOD_ARG);
+ Double[][] extrema = (Double[][]) pb.getObjectParameter(EXTREMA_ARG);
+ ROI roi = (ROI) pb.getObjectParameter(ROI_ARG);
+ Integer[] bands = (Integer[]) pb.getObjectParameter(BAND_ARG);
+ Integer xPeriod = pb.getIntParameter(X_PERIOD_ARG);
+ Integer yPeriod = pb.getIntParameter(Y_PERIOD_ARG);
+ Double noData = (Double) pb.getObjectParameter(NODATA_ARG);
+ Boolean histogram = false;
+ if (pb.getNumParameters() >= 9) {
+ histogram = (Boolean) pb.getObjectParameter(HISTOGRAM_ARG);
+ }
+ Integer histogramBins = 256;
+ if (pb.getNumParameters() >= 10)
+ histogramBins = (Integer) pb.getObjectParameter(HISTOGRAM_BINS);
+
+ switch (method) {
+ case EQUAL_INTERVAL:
+ return new EqualIntervalBreaksOpImage(
+ src, numBins, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod,
+ noData);
+ case QUANTILE:
+ if (histogram) {
+ return new QuantileBreaksHistogramOpImage(
+ src,
+ numBins,
+ extrema,
+ roi,
+ bands,
+ xStart,
+ yStart,
+ xPeriod,
+ yPeriod,
+ noData,
+ histogramBins);
+ } else {
+ return new QuantileBreaksOpImage(
+ src, numBins, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod,
+ noData);
+ }
+ case NATURAL_BREAKS:
+ if (histogram) {
+ return new NaturalBreaksHistogramOpImage(
+ src,
+ numBins,
+ extrema,
+ roi,
+ bands,
+ xStart,
+ yStart,
+ xPeriod,
+ yPeriod,
+ noData,
+ histogramBins);
+ } else {
+ return new NaturalBreaksOpImage(
+ src, numBins, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod,
+ noData);
+ }
+ default:
+ throw new IllegalArgumentException(method.name());
+ }
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/Classification.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/Classification.java
new file mode 100644
index 00000000..e320665c
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/Classification.java
@@ -0,0 +1,96 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+
+import com.sun.javafx.binding.Logging;
+
+import java.util.logging.Logger;
+
+/** Helper class used for raster classification. */
+public class Classification {
+
+ static final Logger LOGGER = Logger.getLogger(Classification.class.getName());
+
+ /** classification method */
+ ClassificationMethod method;
+
+ /** the breaks */
+ Double[][] breaks;
+
+ /** min/max */
+ Double[] min, max;
+
+ public Classification(ClassificationMethod method, int numBands) {
+ this.method = method;
+ this.breaks = new Double[numBands][];
+ this.min = new Double[numBands];
+ this.max = new Double[numBands];
+ }
+
+ public ClassificationMethod getMethod() {
+ return method;
+ }
+
+ public Number[][] getBreaks() {
+ return breaks;
+ }
+
+ public void setBreaks(int b, Double[] breaks) {
+ this.breaks[b] = breaks;
+ }
+
+ public Double getMin(int b) {
+ return min[b];
+ }
+
+ public void setMin(int b, Double min) {
+ this.min[b] = min;
+ }
+
+ public Double getMax(int b) {
+ return max[b];
+ }
+
+ public void setMax(int b, Double max) {
+ this.max[b] = max;
+ }
+
+ public void print() {
+ for (int i = 0; i < breaks.length; i++) {
+ for (Double d : breaks[i]) {
+ LOGGER.info(String.valueOf(d));
+ }
+ }
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassificationMethod.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassificationMethod.java
new file mode 100644
index 00000000..46122e16
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/ClassificationMethod.java
@@ -0,0 +1,48 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+/** Enumeration for method of classifying numeric values into ranges (classes). */
+public enum ClassificationMethod {
+ /** Classifies data into equally sized ranges. */
+ EQUAL_INTERVAL,
+
+ /**
+ * Classifies data into ranges such that the number of values falling into each range is
+ * approximately the same.
+ */
+ QUANTILE,
+
+ /** Classifies data into ranges such that ranges correspond to "clusters" of values. */
+ NATURAL_BREAKS;
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/EqualIntervalBreaksOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/EqualIntervalBreaksOpImage.java
new file mode 100644
index 00000000..12d6424c
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/EqualIntervalBreaksOpImage.java
@@ -0,0 +1,112 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.image.RenderedImage;
+
+import javax.media.jai.ROI;
+
+/** Classification op for the equal interval method. */
+public class EqualIntervalBreaksOpImage extends ClassBreaksOpImage {
+
+ public EqualIntervalBreaksOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData) {
+
+ super(image, numClasses, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod, noData);
+ }
+
+ @Override
+ protected Classification createClassification() {
+ return new Classification(ClassificationMethod.EQUAL_INTERVAL, bands.length);
+ }
+
+ @Override
+ protected Classification preCalculate() {
+ if (extrema != null) {
+ Classification c = createClassification();
+
+ // calculate the bins
+ for (int b = 0; b < bands.length; b++) {
+ double min = extrema[0][b];
+ double max = extrema[1][b];
+
+ c.setMin(b, min);
+ c.setMax(b, max);
+
+ calculateBreaks(c, b);
+ }
+
+ return c;
+ }
+ return null;
+ }
+
+ @Override
+ protected void handleValue(double d, Classification c, int band) {
+ c.setMin(band, c.getMin(band) == null ? d : Math.min(c.getMin(band), d));
+ c.setMax(band, c.getMax(band) == null ? d : Math.max(c.getMax(band), d));
+ }
+
+ @Override
+ protected void postCalculate(Classification c, int band) {
+ calculateBreaks(c, band);
+ }
+
+ void calculateBreaks(Classification c, int band) {
+ Double[] breaks = new Double[numClasses + 1];
+
+ // calculate the breaks
+ double min = c.getMin(band);
+ double max = c.getMax(band);
+
+ double delta = (max - min) / (double) numClasses;
+ double start = min;
+ for (int j = 0; j < numClasses; j++) {
+ breaks[j] = start;
+ start += delta;
+ }
+
+ // last value
+ breaks[numClasses] = max;
+ c.setBreaks(band, breaks);
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/HistogramClassification.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/HistogramClassification.java
new file mode 100644
index 00000000..7bfae239
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/HistogramClassification.java
@@ -0,0 +1,180 @@
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2018, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Classification that collects an histogram of the data instead of single values. Better suited for
+ * large datasets where collecting each single value would require too much memory.
+ */
+public class HistogramClassification extends Classification {
+
+ public static final class Bucket {
+ int count;
+ double average;
+ double min;
+ double max;
+
+ public Bucket(int count, double singleValue) {
+ this.count = count;
+ this.average = this.min = this.max = singleValue;
+ }
+
+ public Bucket(int count, double average, double min, double max) {
+ this.count = count;
+ this.average = average;
+ this.min = min;
+ this.max = max;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public double getAverage() {
+ return average;
+ }
+
+ public double getMin() {
+ return min;
+ }
+
+ public double getMax() {
+ return max;
+ }
+
+ @Override
+ public String toString() {
+ return "Bucket{"
+ + "count="
+ + count
+ + ", average="
+ + average
+ + ", min="
+ + min
+ + ", max="
+ + max
+ + '}';
+ }
+ }
+
+ private final double[] maximums;
+ private final int[][] bucketCounts;
+ private final double[][] bucketAverages;
+ private final boolean[][] bucketSingleValue;
+ private final double[] minimums;
+ private final double[] bucketSize;
+
+ public HistogramClassification(int numBands, Double[][] extrema, int numBins) {
+ super(null, numBands);
+ if (extrema == null) {
+ throw new IllegalArgumentException(
+ "Histogram based classification methods need to be provided with the extrema parameter");
+ }
+ checkExtremaArray(numBands, extrema, 0, "min");
+ checkExtremaArray(numBands, extrema, 1, "max");
+ for (int b = 0; b < numBands; b++) {
+ setMin(b, extrema[0][b]);
+ setMax(b, extrema[1][b]);
+ }
+ this.minimums = Arrays.stream(extrema[0]).mapToDouble(d -> d).toArray();
+ this.maximums = Arrays.stream(extrema[1]).mapToDouble(d -> d).toArray();
+ this.bucketSize = new double[numBands];
+ for (int b = 0; b < numBands; b++) {
+ bucketSize[b] = (extrema[1][b] - extrema[0][b]) / numBins;
+ }
+ bucketCounts = new int[numBands][numBins];
+ bucketAverages = new double[numBands][numBins];
+ bucketSingleValue = new boolean[numBands][numBins];
+ for (int b = 0; b < numBands; b++) {
+ Arrays.fill(bucketSingleValue[b], true);
+ }
+ }
+
+ private void checkExtremaArray(
+ int numBands, Double[][] extrema, int minMax, String minMaxName) {
+ if (extrema[minMax].length < numBands) {
+ throw new IllegalArgumentException(
+ "Illegal extrema array, should have "
+ + minMaxName
+ + " array of "
+ + numBands
+ + " elements but only has "
+ + extrema[minMax].length
+ + " instead");
+ }
+ }
+
+ public void count(double value, int band) {
+ // throw away all elements outside of the desired range
+ double minimum = minimums[band];
+ double maximum = maximums[band];
+ if (value < minimum || value > maximum) {
+ return;
+ }
+
+ // compute bucket involved
+ int idx = (int) ((value - minimum) / bucketSize[band]);
+ // update bucket count and bucket average
+ int[] bucketCount = bucketCounts[band];
+ // on the max value the result might be the n+1 bucket
+ if (idx == bucketCount.length) {
+ idx--;
+ }
+ // increment count
+ bucketCount[idx]++;
+ double[] bucketsAverage = bucketAverages[band];
+ // iterative mean here
+ double average = bucketsAverage[idx];
+ if (bucketCount[idx] > 1) {
+ bucketSingleValue[band][idx] &= (average == value);
+ }
+ bucketsAverage[idx] = average + (value - average) / bucketCount[idx];
+ }
+
+ /**
+ * Returns a list of all non empty buckets
+ *
+ * @return
+ */
+ public List getBuckets(int band) {
+ List buckets = new ArrayList<>();
+ int[] histogram = bucketCounts[band];
+ double[] bucketAverage = bucketAverages[band];
+ double minimum = minimums[band];
+ double size = bucketSize[band];
+ for (int i = 0; i < histogram.length; i++) {
+ if (histogram[i] > 0) {
+ if (bucketSingleValue[band][i]) {
+ buckets.add(new Bucket(histogram[i], bucketAverage[i]));
+ } else {
+ buckets.add(
+ new Bucket(
+ histogram[i],
+ bucketAverage[i],
+ minimum + i * size,
+ minimum + (i + 1) * size));
+ }
+ }
+ }
+
+ return buckets;
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksHistogramOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksHistogramOpImage.java
new file mode 100644
index 00000000..3f96f6a3
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksHistogramOpImage.java
@@ -0,0 +1,170 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2018, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.image.RenderedImage;
+import java.util.List;
+
+import javax.media.jai.ROI;
+
+import it.geosolutions.jaiext.classbreaks.HistogramClassification.Bucket;
+
+/** Classification op for the natural breaks method. */
+public class NaturalBreaksHistogramOpImage extends ClassBreaksOpImage {
+
+ int numBins;
+
+ public NaturalBreaksHistogramOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData,
+ int numBins) {
+ super(image, numClasses, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod, noData);
+ this.numBins = numBins;
+ }
+
+ @Override
+ protected Classification createClassification() {
+ return new HistogramClassification(bands.length, extrema, numBins);
+ }
+
+ @Override
+ protected void handleValue(double d, Classification c, int band) {
+ ((HistogramClassification) c).count(d, band);
+ }
+
+ @Override
+ protected void postCalculate(Classification c, int band) {
+ HistogramClassification hc = (HistogramClassification) c;
+ List buckets = hc.getBuckets(band);
+
+ final int k = numClasses;
+ final int m = buckets.size();
+
+ if (k >= m) {
+ // just return all the values
+ Double[] breaks = new Double[m + 1];
+ for (int i = 0; i < m; i++) {
+ breaks[i] = buckets.get(i).getMin();
+ }
+ breaks[m] = buckets.get(m - 1).getMax();
+ c.setBreaks(band, breaks);
+ return;
+ }
+
+ int[][] iwork = new int[m + 1][k + 1];
+ double[][] work = new double[m + 1][k + 1];
+
+ for (int j = 1; j <= k; j++) {
+ // the first item is always in the first class!
+ iwork[0][j] = 1;
+ iwork[1][j] = 1;
+ // initialize work matirix
+ work[1][j] = 0;
+ for (int i = 2; i <= m; i++) {
+ work[i][j] = Double.MAX_VALUE;
+ }
+ }
+
+ // calculate the class for each data item
+ for (int i = 1; i <= m; i++) {
+ // sum of data values
+ double s1 = 0;
+ // sum of squares of data values
+ double s2 = 0;
+
+ double var = 0.0;
+ int totalCount = 0;
+ // consider all the previous values
+ for (int ii = 1; ii <= i; ii++) {
+ // index in to sorted data array
+ int i3 = i - ii + 1;
+ // remember to allow for 0 index
+ Bucket bucket = buckets.get(i3 - 1);
+ double average = bucket.getAverage();
+ int count = bucket.getCount();
+ // double squaredSum = bucket.getSquaredSum();
+ // update running totals
+ // ... adding the sum of all squares contained in the bucket
+ s2 = s2 + (average * average * count);
+ // ... adding the sum of all values contained in the bucket
+ s1 += average * count;
+ totalCount += count;
+ double s0 = totalCount;
+ // calculate (square of) the variance
+ // (http://secure.wikimedia.org/wikipedia/en/wiki/Standard_deviation#Rapid_calculation_methods)
+ var = s2 - ((s1 * s1) / s0);
+ // System.out.println(s0+" "+s1+" "+s2);
+ // System.out.println(i+","+ii+" var "+var);
+ int ik = i3 - 1;
+ if (ik != 0) {
+ // not the last value
+ for (int j = 2; j <= k; j++) {
+ // for each class compare current value to var + previous value
+ // System.out.println("\tis "+work[i][j]+" >= "+(var + work[ik][j - 1]));
+ if (work[i][j] >= (var + work[ik][j - 1])) {
+ // if it is greater or equal update classification
+ iwork[i][j] = i3 - 1;
+ // System.out.println("\t\tiwork["+i+"]["+j+"] = "+i3);
+ work[i][j] = var + work[ik][j - 1];
+ }
+ }
+ }
+ }
+ // store the latest variance!
+ iwork[i][1] = 1;
+ work[i][1] = var;
+ }
+
+ Double[] breaks = new Double[k + 1];
+
+ // go through matrix and extract class breaks
+ int ik = m - 1;
+ breaks[k] = buckets.get(ik).getMax();
+ for (int j = k; j >= 2; j--) {
+ int id =
+ (int) iwork[ik][j] - 1; // subtract one as we want inclusive breaks on the left?
+ breaks[j - 1] = buckets.get(id).getAverage();
+ ik = (int) iwork[ik][j] - 1;
+ }
+ breaks[0] = buckets.get(0).getMin();
+ hc.setBreaks(band, breaks);
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksOpImage.java
new file mode 100644
index 00000000..ea2c323d
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalBreaksOpImage.java
@@ -0,0 +1,163 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.image.RenderedImage;
+import java.util.Collections;
+import java.util.List;
+
+import javax.media.jai.ROI;
+
+/** Classification op for the natural breaks method. */
+public class NaturalBreaksOpImage extends ClassBreaksOpImage {
+
+ public NaturalBreaksOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData) {
+ super(image, numClasses, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod, noData);
+ }
+
+ @Override
+ protected Classification createClassification() {
+ return new NaturalClassification(bands.length);
+ }
+
+ @Override
+ protected void handleValue(double d, Classification c, int band) {
+ if (extrema != null) {
+ double min = extrema[0][band];
+ double max = extrema[1][band];
+
+ if (d < min || d > max) {
+ return;
+ }
+ }
+ ((NaturalClassification) c).count(d, band);
+ }
+
+ @Override
+ protected void postCalculate(Classification c, int band) {
+ NaturalClassification nc = (NaturalClassification) c;
+
+ List data = nc.getValues(band);
+ Collections.sort(data);
+
+ final int k = numClasses;
+ final int m = data.size();
+
+ if (k >= m) {
+ // just return all the values
+ c.setBreaks(band, data.toArray(new Double[data.size()]));
+ return;
+ }
+
+ int[][] iwork = new int[m + 1][k + 1];
+ double[][] work = new double[m + 1][k + 1];
+
+ for (int j = 1; j <= k; j++) {
+ // the first item is always in the first class!
+ iwork[0][j] = 1;
+ iwork[1][j] = 1;
+ // initialize work matirix
+ work[1][j] = 0;
+ for (int i = 2; i <= m; i++) {
+ work[i][j] = Double.MAX_VALUE;
+ }
+ }
+
+ // calculate the class for each data item
+ for (int i = 1; i <= m; i++) {
+ // sum of data values
+ double s1 = 0;
+ // sum of squares of data values
+ double s2 = 0;
+
+ double var = 0.0;
+ // consider all the previous values
+ for (int ii = 1; ii <= i; ii++) {
+ // index in to sorted data array
+ int i3 = i - ii + 1;
+ // remember to allow for 0 index
+ double val = data.get(i3 - 1);
+ // update running totals
+ s2 = s2 + (val * val);
+ s1 += val;
+ double s0 = (double) ii;
+ // calculate (square of) the variance
+ // (http://secure.wikimedia.org/wikipedia/en/wiki/Standard_deviation#Rapid_calculation_methods)
+ var = s2 - ((s1 * s1) / s0);
+ // System.out.println(s0+" "+s1+" "+s2);
+ // System.out.println(i+","+ii+" var "+var);
+ int ik = i3 - 1;
+ if (ik != 0) {
+ // not the last value
+ for (int j = 2; j <= k; j++) {
+ // for each class compare current value to var + previous value
+ // System.out.println("\tis "+work[i][j]+" >= "+(var + work[ik][j - 1]));
+ if (work[i][j] >= (var + work[ik][j - 1])) {
+ // if it is greater or equal update classification
+ iwork[i][j] = i3 - 1;
+ // System.out.println("\t\tiwork["+i+"]["+j+"] = "+i3);
+ work[i][j] = var + work[ik][j - 1];
+ }
+ }
+ }
+ }
+ // store the latest variance!
+ iwork[i][1] = 1;
+ work[i][1] = var;
+ }
+
+ Double[] breaks = new Double[k + 1];
+
+ // go through matrix and extract class breaks
+ int ik = m - 1;
+ breaks[k] = data.get(ik);
+ for (int j = k; j >= 2; j--) {
+ int id = (int) iwork[ik][j] - 1; // subtract one as we want inclusive breaks on the
+ // left?
+ breaks[j - 1] = data.get(id);
+ ik = (int) iwork[ik][j] - 1;
+ }
+ breaks[0] = data.get(0);
+ nc.setBreaks(band, breaks);
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalClassification.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalClassification.java
new file mode 100644
index 00000000..72b547cc
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/NaturalClassification.java
@@ -0,0 +1,58 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Helper class used for raster natural breaks classification. */
+public class NaturalClassification extends Classification {
+
+ List[] values;
+
+ public NaturalClassification(int numBands) {
+ super(ClassificationMethod.NATURAL_BREAKS, numBands);
+ values = new List[numBands];
+ for (int i = 0; i < values.length; i++) {
+ values[i] = new ArrayList<>();
+ }
+ }
+
+ public void count(double value, int band) {
+ values[band].add(value);
+ }
+
+ public List getValues(int band) {
+ return values[band];
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksHistogramOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksHistogramOpImage.java
new file mode 100644
index 00000000..e78ba17e
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksHistogramOpImage.java
@@ -0,0 +1,109 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2018, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+
+import java.awt.image.RenderedImage;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TreeSet;
+
+import javax.media.jai.ROI;
+
+import it.geosolutions.jaiext.classbreaks.HistogramClassification.Bucket;
+
+/**
+ * Classification op for the quantile method, using histograms instead of a fully developed list of
+ * values
+ */
+public class QuantileBreaksHistogramOpImage extends ClassBreaksOpImage {
+
+ int numBins;
+
+ public QuantileBreaksHistogramOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData,
+ int numBins) {
+ super(image, numClasses, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod, noData);
+ this.numBins = numBins;
+ }
+
+ @Override
+ protected Classification createClassification() {
+ return new HistogramClassification(bands.length, extrema, numBins);
+ }
+
+ @Override
+ protected void handleValue(double d, Classification c, int band) {
+ ((HistogramClassification) c).count(d, band);
+ }
+
+ @Override
+ protected void postCalculate(Classification c, int band) {
+ HistogramClassification hc = (HistogramClassification) c;
+ List buckets = hc.getBuckets(band);
+
+ // calculate the number of values per class
+ int nvalues = buckets.stream().mapToInt(b -> b.getCount()).sum();
+ int size = (int) Math.ceil(nvalues / (double) numClasses);
+
+ // grab the key iterator
+ Iterator it = buckets.iterator();
+
+ TreeSet set = new TreeSet();
+ Bucket e = it.next();
+
+ int classIdx = 1;
+ int count = 0;
+ set.add(e.getMin());
+ while (classIdx < numClasses && it.hasNext()) {
+ count += e.getCount();
+ e = it.next();
+
+ if (count >= (size * classIdx)) {
+ classIdx++;
+ set.add(e.getMin());
+ }
+ }
+ set.add(buckets.get(buckets.size() - 1).getMax());
+ hc.setBreaks(band, set.toArray(new Double[set.size()]));
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksOpImage.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksOpImage.java
new file mode 100644
index 00000000..679db4c2
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileBreaksOpImage.java
@@ -0,0 +1,121 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.awt.image.RenderedImage;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeSet;
+
+import javax.media.jai.ROI;
+
+/** Classification op for the quantile method. */
+public class QuantileBreaksOpImage extends ClassBreaksOpImage {
+
+ public QuantileBreaksOpImage(
+ RenderedImage image,
+ Integer numClasses,
+ Double[][] extrema,
+ ROI roi,
+ Integer[] bands,
+ Integer xStart,
+ Integer yStart,
+ Integer xPeriod,
+ Integer yPeriod,
+ Double noData) {
+ super(image, numClasses, extrema, roi, bands, xStart, yStart, xPeriod, yPeriod, noData);
+ }
+
+ @Override
+ protected Classification createClassification() {
+ return new QuantileClassification(bands.length);
+ }
+
+ @Override
+ protected void handleValue(double d, Classification c, int band) {
+ QuantileClassification qc = (QuantileClassification) c;
+ if (extrema != null) {
+ double min = extrema[0][band];
+ double max = extrema[1][band];
+
+ if (d < min || d > max) {
+ return;
+ }
+ }
+
+ qc.count(d, band);
+ }
+
+ @Override
+ protected void postCalculate(Classification c, int band) {
+ QuantileClassification qc = (QuantileClassification) c;
+
+ // get the total number of values
+ int nvalues = qc.getCount(band);
+
+ // calculate the number of values per class
+ int size = (int) Math.ceil(nvalues / (double) numClasses);
+
+ // grab the key iterator
+ Iterator> it = qc.getTable(band).entrySet().iterator();
+
+ TreeSet set = new TreeSet();
+ Map.Entry e = it.next();
+
+ while (nvalues > 0) {
+ // add the next break
+ set.add(e.getKey());
+
+ for (int i = 0; i < size && nvalues > 0; i++) {
+ // consume the next value
+ int count = e.getValue();
+ e.setValue(--count);
+ nvalues--;
+
+ if (count == 0) {
+ // number of occurences of this entry exhausted, move to next
+ if (!it.hasNext()) {
+ break;
+ }
+ e = it.next();
+ }
+ }
+
+ if (nvalues == 0) {
+ // add the last value
+ set.add(e.getKey());
+ }
+ }
+ qc.setBreaks(band, set.toArray(new Double[set.size()]));
+ }
+}
diff --git a/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileClassification.java b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileClassification.java
new file mode 100644
index 00000000..cba4db61
--- /dev/null
+++ b/jt-classbreaks/src/main/java/it/geosolutions/jaiext/classbreaks/QuantileClassification.java
@@ -0,0 +1,84 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+ * http://www.geo-solutions.it/
+ * Copyright 2018 GeoSolutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2016, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+
+/** Helper class used for raster quantile classification. */
+public class QuantileClassification extends Classification {
+
+ static final Logger LOGGER = Logger.getLogger(Classification.class.getName());
+
+ int[] counts;
+ SortedMap[] tables;
+
+ public QuantileClassification(int numBands) {
+ super(ClassificationMethod.QUANTILE, numBands);
+ counts = new int[numBands];
+ tables = new SortedMap[numBands];
+ }
+
+ public void count(double value, int band) {
+ counts[band]++;
+
+ SortedMap table = getTable(band);
+
+ Integer count = table.get(value);
+ table.put(value, count != null ? new Integer(count + 1) : new Integer(1));
+ }
+
+ public SortedMap getTable(int band) {
+ SortedMap table = tables[band];
+ if (table == null) {
+ table = new TreeMap();
+ tables[band] = table;
+ }
+ return table;
+ }
+
+ public int getCount(int band) {
+ return counts[band];
+ }
+
+ void printTable() {
+ for (int i = 0; i < tables.length; i++) {
+ SortedMap table = getTable(i);
+ for (Entry e : table.entrySet()) {
+ LOGGER.info(String.format("%f: %d", e.getKey(), e.getValue()));
+ }
+ }
+ }
+}
diff --git a/jt-classbreaks/src/main/resources/META-INF/registryFile.jaiext b/jt-classbreaks/src/main/resources/META-INF/registryFile.jaiext
new file mode 100644
index 00000000..edad44da
--- /dev/null
+++ b/jt-classbreaks/src/main/resources/META-INF/registryFile.jaiext
@@ -0,0 +1,9 @@
+#
+# Image descriptors
+#
+descriptor it.geosolutions.jaiext.classbreaks.ClassBreaksDescriptor
+
+#
+# RenderedImageFactories
+#
+rendered it.geosolutions.jaiext.classbreaks.ClassBreaksRIF it.geosolutions.jaiext ClassBreaks ClassBreaks
diff --git a/jt-classbreaks/src/test/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImageTest.java b/jt-classbreaks/src/test/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImageTest.java
new file mode 100644
index 00000000..afdffdb1
--- /dev/null
+++ b/jt-classbreaks/src/test/java/it/geosolutions/jaiext/classbreaks/ClassBreaksOpImageTest.java
@@ -0,0 +1,215 @@
+/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
+* http://www.geo-solutions.it/
+* Copyright 2018 GeoSolutions
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+
+* http://www.apache.org/licenses/LICENSE-2.0
+
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+/*
+ * GeoTools - The Open Source Java GIS Toolkit
+ * http://geotools.org
+ *
+ * (C) 2018, Open Source Geospatial Foundation (OSGeo)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ */
+package it.geosolutions.jaiext.classbreaks;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+
+import java.awt.*;
+import java.awt.image.RenderedImage;
+import java.util.Arrays;
+
+import javax.media.jai.JAI;
+import javax.media.jai.ParameterBlockJAI;
+import javax.media.jai.RenderedOp;
+import javax.media.jai.operator.ExtremaDescriptor;
+
+import it.geosolutions.jaiext.JAIExt;
+import it.geosolutions.jaiext.testclasses.TestBase;
+import it.geosolutions.jaiext.utilities.ImageUtilities;
+
+public class ClassBreaksOpImageTest extends TestBase {
+
+ static final double EPS = 1e-3;
+
+ static RenderedImage createImage() {
+ return ImageUtilities.createImageFromArray(
+ new Number[] {1, 1, 2, 3, 3, 8, 8, 9, 11, 14, 16, 24, 26, 26, 45, 53}, 4, 4);
+ }
+
+ @Test
+ public void getMissingProperty() {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.QUANTILE);
+ pb.setParameter("numClasses", 5);
+ // raw creation like in CoverageClassStats, otherwise the issue gets masked by JAI wrappers
+ RenderedImage op = new ClassBreaksRIF().create(pb, null);
+
+ // used to NPE here
+ Object roi = op.getProperty("ROI");
+ assertEquals(Image.UndefinedProperty, roi);
+ }
+
+ @Test
+ public void testEqualInterval() throws Exception {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.EQUAL_INTERVAL);
+ pb.setParameter("numClasses", 4);
+ RenderedImage op = JAI.create("ClassBreaks", pb, null);
+ Classification classification =
+ (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ assertNotNull(classification);
+ Number[] breaks = classification.getBreaks()[0];
+
+ assertEquals(5, breaks.length);
+ assertEquals(1, breaks[0].doubleValue(), EPS);
+ assertEquals(14, breaks[1].doubleValue(), EPS);
+ assertEquals(27, breaks[2].doubleValue(), EPS);
+ assertEquals(40, breaks[3].doubleValue(), EPS);
+ assertEquals(53, breaks[4].doubleValue(), EPS);
+ }
+
+ @Test
+ public void testQuantileBreaks() throws Exception {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.QUANTILE);
+ pb.setParameter("numClasses", 4);
+ RenderedImage op = JAI.create("ClassBreaks", pb, null);
+ Classification classification =
+ (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ assertNotNull(classification);
+ Number[] breaks = classification.getBreaks()[0];
+
+ // 4 classes, 5 breaks
+ // 1, 1, 2,
+ // 3, 3, 8, 8, 9,
+ // 11, 14, 16, 24,
+ // 26, 26, 45, 53
+ assertEquals(5, breaks.length);
+ assertEquals(1, breaks[0].doubleValue(), EPS);
+ assertEquals(3, breaks[1].doubleValue(), EPS);
+ assertEquals(11, breaks[2].doubleValue(), EPS);
+ assertEquals(26, breaks[3].doubleValue(), EPS);
+ assertEquals(53, breaks[4].doubleValue(), EPS);
+ }
+
+ @Test
+ public void testQuantileBreaksHistogram() throws Exception {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.QUANTILE);
+ pb.setParameter("numClasses", 4);
+ pb.setParameter("extrema", getExtrema(image));
+ pb.setParameter("histogram", true);
+ pb.setParameter("histogramBins", 100);
+ RenderedImage op = JAI.create("ClassBreaks", pb, null);
+ Classification classification =
+ (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ assertNotNull(classification);
+ Number[] breaks = classification.getBreaks()[0];
+
+ // 4 classes, 5 breaks (not the same as the exact count, slightly different approach,
+ // but still correct)
+ // 1, 1, 2, 3, 3,
+ // 8, 8, 9,
+ // 11, 14, 16, 24,
+ // 26, 26, 45, 53
+ assertEquals(5, breaks.length);
+ assertEquals(1, breaks[0].doubleValue(), EPS);
+ assertEquals(8, breaks[1].doubleValue(), EPS);
+ assertEquals(11, breaks[2].doubleValue(), EPS);
+ assertEquals(26, breaks[3].doubleValue(), EPS);
+ assertEquals(53, breaks[4].doubleValue(), EPS);
+ }
+
+ @Test
+ public void testNaturalBreaks() throws Exception {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.NATURAL_BREAKS);
+ pb.setParameter("numClasses", 4);
+ RenderedImage op = JAI.create("ClassBreaks", pb, null);
+ Classification classification =
+ (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ assertNotNull(classification);
+ Number[] breaks = classification.getBreaks()[0];
+
+ // 4 classes, 5 breaks
+ assertEquals(5, breaks.length);
+ assertEquals(1, breaks[0].doubleValue(), EPS);
+ assertEquals(3, breaks[1].doubleValue(), EPS);
+ assertEquals(16, breaks[2].doubleValue(), EPS);
+ assertEquals(26, breaks[3].doubleValue(), EPS);
+ assertEquals(53, breaks[4].doubleValue(), EPS);
+ }
+
+ @Test
+ public void testNaturalBreaksHistogram() throws Exception {
+ RenderedImage image = createImage();
+
+ ParameterBlockJAI pb = new ParameterBlockJAI(new ClassBreaksDescriptor());
+ pb.addSource(image);
+ pb.setParameter("method", ClassificationMethod.NATURAL_BREAKS);
+ pb.setParameter("numClasses", 4);
+ pb.setParameter("extrema", getExtrema(image));
+ pb.setParameter("histogram", true);
+ pb.setParameter("histogramBins", 100);
+ RenderedImage op = JAI.create("ClassBreaks", pb, null);
+ Classification classification =
+ (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY);
+ assertNotNull(classification);
+ Number[] breaks = classification.getBreaks()[0];
+
+ // 4 classes, 5 breaks
+ assertEquals(5, breaks.length);
+ assertEquals(1, breaks[0].doubleValue(), EPS);
+ assertEquals(3, breaks[1].doubleValue(), EPS);
+ assertEquals(16, breaks[2].doubleValue(), EPS);
+ assertEquals(26, breaks[3].doubleValue(), EPS);
+ assertEquals(53, breaks[4].doubleValue(), EPS);
+ }
+
+ private Double[][] getExtrema(RenderedImage image) {
+ RenderedOp extremaOp = ExtremaDescriptor.create(image, null, 1, 1, false, 1, null);
+ double[][] extrema= (double[][]) extremaOp.getProperty("extrema");
+ Double[][] result = new Double[2][];
+ result[0] = new Double[] {extrema[0][0]};
+ result[1] = new Double[] {extrema[1][0]};
+ return result;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 4db58620..44129cad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -156,6 +156,7 @@
it.geosolutions.jaiext.threshold
it.geosolutions.jaiext.clamp
it.geosolutions.jaiext.shadedrelief
+ it.geosolutions.jaiext.classbreaks
jt-concurrent-tile-cache,
@@ -193,6 +194,7 @@
jt-threshold
jt-clamp
jt-shadedrelief
+ jt-classbreaks
@@ -730,5 +732,6 @@
jt-scale2
jt-shadedrelief
jt-jiffle
+ jt-classbreaks