org.orbisgis.view.toc.actions.cui.legend.stats.Thresholds.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.view.toc.actions.cui.legend.stats.Thresholds.java

Source

/**
 * OrbisGIS is a java GIS application dedicated to research in GIScience.
 * OrbisGIS is developed by the GIS group of the DECIDE team of the 
 * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
 *
 * The GIS group of the DECIDE team is located at :
 *
 * Laboratoire Lab-STICC  CNRS UMR 6285
 * Equipe DECIDE
 * UNIVERSIT DE BRETAGNE-SUD
 * Institut Universitaire de Technologie de Vannes
 * 8, Rue Montaigne - BP 561 56017 Vannes Cedex
 * 
 * OrbisGIS is distributed under GPL 3 license.
 *
 * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
 * Copyright (C) 2015-2016 CNRS (Lab-STICC UMR CNRS 6285)
 *
 * This file is part of OrbisGIS.
 *
 * OrbisGIS is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * OrbisGIS 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, please consult: <http://www.orbisgis.org/>
 * or contact directly:
 * info_at_ orbisgis.org
 */
package org.orbisgis.view.toc.actions.cui.legend.stats;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.orbisgis.coremap.renderer.se.parameter.Categorize;

import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * This class uses statistics on a field to compute thresholds.
 * @author Alexis Guganno
 */
public class Thresholds {
    private DescriptiveStatistics stats;
    private String fieldName;

    /**
     * Builds a new {@code Thresholds} instance using the given {@code DescriptiveStatistics} and {@code String} instance.
     * @param input The computed statistics.
     * @param name The name of the field we took the data from.
     */
    public Thresholds(DescriptiveStatistics input, String name) {
        this.stats = input;
        this.fieldName = name;
    }

    /**
     * Gets {@code classNumber} of methods according to the given classification
     * @param method The classification method
     * @param classNumber The number of classes
     * @return The thresholds in a SortedSet
     */
    public SortedSet<Double> getThresholds(Categorize.CategorizeMethod method, int classNumber) {
        switch (method) {
        case EQUAL_INTERVAL:
            return getEqualIntervals(classNumber);
        case STANDARD_DEVIATION:
            return getMeanStandardDev(classNumber);
        case QUANTILES:
            return getQuantiles(classNumber);
        case BOXED_MEANS:
            return getBoxedMeans(classNumber);
        default:
            throw new UnsupportedOperationException("This method is not supported");
        }
    }

    /**
     * Divide the space between and max in {@code classNumber} equal intervals. The returned set does not contain
     * negative infinity : the first and last thresholds match the extrema of the input data.
     * @param classNumber The number of classes
     * @return The thresholds in a SortedSet.
     */
    public SortedSet<Double> getEqualIntervals(int classNumber) {
        Double min = stats.getMin();
        Double max = stats.getMax();
        TreeSet<Double> ret = new TreeSet<Double>();
        if (min < Double.POSITIVE_INFINITY && max > Double.NEGATIVE_INFINITY) {
            Double step = (max - min) / classNumber;
            for (int i = 0; i < classNumber; i++) {
                ret.add(min + step * i);
            }
        }
        return ret;
    }

    /**
     * Gets {@code classNumber} thresholds computed according to the Mean - Standard Deviation method. The returned set
     * contains negative infinity as this classification method will likely produce thresholds that do not match the
     * extrema of the input set.
     * @param classNumber The number of classes.
     * @return The thresholds.
     */
    public SortedSet<Double> getMeanStandardDev(int classNumber) {
        if (classNumber % 2 == 0) {
            return getMeanStandardDevEven(classNumber);
        } else {
            return getMeanStandardDevOdd(classNumber);
        }
    }

    /**
     * Retrieve the thresholds for a quantile classification. The first threshold is the minimum value of the input set.
     * Thresholds are computed using the percentile computation capabilities of Apache commons-math.
     * @param classNumber The number of classes.
     * @return The thresholds.
     */
    public SortedSet<Double> getQuantiles(int classNumber) {
        Double step = 100 / ((double) classNumber);
        TreeSet<Double> ret = new TreeSet<Double>();
        Double min = stats.getMin();
        ret.add(min);
        for (int i = 1; i < classNumber; i++) {
            double p = i * step;
            ret.add(stats.getPercentile(p));
        }
        return ret;
    }

    /**
     * Gets a boxed means analysis using the provided data. If {@code classNumber} is not a power of two,
     * the greatest power of two that is lower than it will be used.
     * @param classNumber The number of classes
     * @return The thresholds
     */
    public SortedSet<Double> getBoxedMeans(int classNumber) {
        SortedSet<Double> ret = new TreeSet<Double>();
        ret.add(stats.getMin());
        int levels = classNumber == 0 ? 0 : 32 - Integer.numberOfLeadingZeros(classNumber) - 1;
        computeBoxedMeans(stats, ret, levels - 1);
        return ret;
    }

    /**
     * This method :
     * - Feeds the given SortedSet with the mean of the given statistics.
     * - Calls itself recursively on the two subset obtained by dividing the set around its mean, if lev > 0.
     * @param inpStat The input statistics
     * @param toFeed The SortedSet we want to feed
     * @param lev The remaining number of levels we have to process.
     */
    private void computeBoxedMeans(DescriptiveStatistics inpStat, SortedSet<Double> toFeed, int lev) {
        double[] input = inpStat.getSortedValues();
        double mean = inpStat.getMean();
        toFeed.add(mean);
        if (lev > 0) {
            int i = Arrays.binarySearch(input, mean);
            int ind = i < 0 ? -i - 1 : i;
            double[] first = Arrays.copyOf(input, ind);
            double[] tail = Arrays.copyOfRange(input, ind, input.length);
            computeBoxedMeans(new DescriptiveStatistics(first), toFeed, lev - 1);
            computeBoxedMeans(new DescriptiveStatistics(tail), toFeed, lev - 1);
        }
    }

    /**
     * Gets thresholds for an odd number of classes computed according to the Mean - Standard Deviation method.
     * @param classNumber The number of classes. Shall be odd.
     * @return The thresholds
     */
    private SortedSet<Double> getMeanStandardDevOdd(int classNumber) {
        Double mean = stats.getMean();
        Double stDev = stats.getStandardDeviation();
        SortedSet<Double> ret = new TreeSet<Double>();
        ret.add(Double.NEGATIVE_INFINITY);
        if (classNumber > 1) {
            int num = (classNumber - 1) / 2;
            for (int i = 0; i < num; i++) {
                ret.add(mean + (i + 0.5) * stDev);
                ret.add(mean - (i + 0.5) * stDev);
            }
        }
        return ret;
    }

    /**
     * Gets thresholds for an even number of classes computed according to the Mean - Standard Deviation method.
     * @param classNumber The number of classes. Shall be even.
     * @return The thresholds
     */
    private SortedSet<Double> getMeanStandardDevEven(int classNumber) {
        Double mean = stats.getMean();
        Double stDev = stats.getStandardDeviation();
        SortedSet<Double> ret = new TreeSet<Double>();
        ret.add(Double.NEGATIVE_INFINITY);
        if (classNumber > 0) {
            ret.add(mean);
        }
        if (classNumber > 2) {
            int num = classNumber / 2 - 1;
            for (int i = 1; i <= num; i++) {
                ret.add(mean + i * stDev);
                ret.add(mean - i * stDev);
            }
        }
        return ret;
    }

    /**
     * Gets the name of the field associated to this {@code Thresholds} instance.
     * @return The name of the field that had been used to compute stats.
     */
    public String getFieldName() {
        return fieldName;
    }

}