/* * 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 org.geotools.process.raster; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import javax.media.jai.JAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.operator.BandSelectDescriptor; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.process.ProcessException; import org.geotools.process.classify.ClassificationMethod; import org.geotools.process.raster.classify.Classification; import org.geotools.process.classify.ClassificationStats; import org.geotools.process.factory.DescribeParameter; import org.geotools.process.factory.DescribeProcess; import org.geotools.process.factory.DescribeResult; import org.geotools.processing.jai.ClassBreaksDescriptor; import org.geotools.processing.jai.ClassBreaksRIF; import org.geotools.renderer.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.jaitools.media.jai.zonalstats.Result; import org.jaitools.media.jai.zonalstats.ZonalStats; import org.jaitools.media.jai.zonalstats.ZonalStatsDescriptor; import org.jaitools.numeric.Range; import org.jaitools.numeric.Statistic; import org.opengis.util.ProgressListener; /** * Process that classifies vector data into "classes" using one of the following methods: * <ul> * <li>Equal Interval ({@link ClassificationMethod#EQUAL_INTERVAL})</li> * <li>Quantile ({@link ClassificationMethod#QUANTILE})</li> * <li>Natural Breaks ({@link ClassificationMethod#NATURAL_BREAKS})</li> * </ul> * */ @DescribeProcess(title = "coverageClassStats", description = "Calculates statistics from coverage" + " values classified into bins/classes.") public class CoverageClassStats implements RasterProcess { @DescribeResult(name = "results", description = "The classified results") public Results execute( @DescribeParameter(name = "coverage", description = "The coverage to analyze") GridCoverage2D coverage, @DescribeParameter(name = "stats", description = "The statistics to calculate for each class", collectionType = Statistic.class, min = 0) Set<Statistic> stats, @DescribeParameter(name = "band", description = "The band to calculate breaks/statistics for", min = 0) Integer band, @DescribeParameter(name = "classes", description = "The number of breaks/classes", min = 0) Integer classes, @DescribeParameter(name = "method", description = "The classification method", min = 0) ClassificationMethod method, @DescribeParameter(name = "noData", description = "The pixel value to be ommitted from any calculation", min = 0 ) Double noData, ProgressListener progressListener) throws ProcessException, IOException { // // initial checks/defaults // if(coverage==null){ throw new ProcessException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"coverage")); } if (classes == null) { classes = 10; } if (classes < 1) { throw new ProcessException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "classes", classes)); } RenderedImage sourceImage = coverage.getRenderedImage(); // parse the band if (band == null) { band = 0; } final int numBands = sourceImage.getSampleModel().getNumBands(); if(band < 0 || band >= numBands){ throw new ProcessException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,"band",band)); } if (numBands > 1) { sourceImage=BandSelectDescriptor.create(sourceImage, new int []{band}, null); } //other defaults if (method == null) { method = ClassificationMethod.EQUAL_INTERVAL; } if (stats == null || stats.isEmpty()) { stats = Collections.singleton(Statistic.MEAN); } //compute the class breaks //JD: for some reason running this in tomcat via JAI.create does not work, the operation // descriptor is never registered and it fails, so for now we just invoke the op directly // TODO: look into this more ParameterBlock pb = new ParameterBlock(); pb.addSource(sourceImage); pb.set(classes, 0); pb.set(method, 1); pb.set(null, 2); pb.set(null, 3); pb.set(new Integer[]{0}, 4); pb.set(1, 5); pb.set(1, 6); pb.set(noData, 7); RenderedImage op = new ClassBreaksRIF().create(pb, null); /*ParameterBlockJAI pb = new ParameterBlockJAI(ClassBreaksDescriptor.NAME); pb.setParameter("numClasses", classes); pb.setParameter("method", method); if (noData != null) { pb.setParameter("noData", noData); } RenderedOp op = JAI.create(ClassBreaksDescriptor.NAME, pb);*/ Classification c = (Classification) op.getProperty(ClassBreaksDescriptor.CLASSIFICATION_PROPERTY); Double[] breaks = (Double[]) c.getBreaks()[0]; //build up the classes/ranges List<Range<Double>> ranges = new ArrayList<Range<Double>>(); for (int i = 0; i < breaks.length-1; i++) { ranges.add(Range.create(breaks[i], true, breaks[i+1], i == breaks.length-2)); } //calculate stats for each class ParameterBlockJAI pbj = new ParameterBlockJAI("ZonalStats"); pbj.addSource(sourceImage); pbj.setParameter("stats", stats.toArray(new Statistic[stats.size()])); pbj.setParameter("bands", new Integer[]{band}); pbj.setParameter("ranges", ranges); pbj.setParameter("rangesType", Range.Type.INCLUDE); pbj.setParameter("rangeLocalStats", true); // "bands", // "roi", // "zoneTransform", // "ranges", // "rangesType", // "rangeLocalStats", // "noDataRanges" op = JAI.create("ZonalStats", pbj); ZonalStats zonalStats = (ZonalStats) op.getProperty(ZonalStatsDescriptor.ZONAL_STATS_PROPERTY); return new Results(stats, zonalStats); } public static class Results implements ClassificationStats { Statistic firstStat; Set<Statistic> stats; ZonalStats zonalStats; List<Result> ranges; public Results(Set<Statistic> stats, ZonalStats zonalStats) { this.stats = stats; this.zonalStats = zonalStats; this.firstStat = stats.iterator().next(); ranges = zonalStats.statistic(firstStat).results(); } public int size() { return ranges.size(); } public Set<Statistic> getStats() { return stats; } public Range range(int i) { return ranges.get(i).getRanges().iterator().next(); } public Double value(int i, Statistic stat) { return zonalStats.statistic(stat).results().get(i).getValue(); } public Long count(int i) { return zonalStats.statistic(firstStat).results().get(i).getNumAccepted(); } ZonalStats getZonalStats() { return zonalStats; } public void print() { for (int i = 0; i < size(); i++) { System.out.println(range(i)); for (Statistic stat : stats) { System.out.println(stat + " = " + value(i, stat)); } } } } }