/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2008, 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.filter.function;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.visitor.MaxVisitor;
import org.geotools.feature.visitor.MinVisitor;
import org.geotools.feature.visitor.UniqueVisitor;
import org.geotools.filter.IllegalFilterException;
import org.geotools.util.NullProgressListener;
/**
* Classification function for breaking a feature collection into edible chunks
* of "equal" size.
*
* @author James Macgill
* @author Cory Horner, Refractions Research Inc.
*
* @source $URL$
*/
public class EqualIntervalFunction extends ClassificationFunction {
public EqualIntervalFunction() {
setName("EqualInterval");
}
private RangedClassifier calculate(SimpleFeatureCollection featureCollection) {
int classNum = getClasses();
Comparable globalMin;
Comparable globalMax;
try {
MinVisitor minVisit = new MinVisitor(getExpression());
if (progress == null) progress = new NullProgressListener();
featureCollection.accepts(minVisit, progress);
if (progress.isCanceled()) return null;
globalMin = (Comparable) minVisit.getResult().getValue();
MaxVisitor maxVisit = new MaxVisitor(getExpression());
featureCollection.accepts(maxVisit, progress);
if (progress.isCanceled()) return null;
globalMax = (Comparable) maxVisit.getResult().getValue();
if ((globalMin instanceof Number) && (globalMax instanceof Number)) {
return calculateNumerical(classNum, globalMin, globalMax);
} else {
return calculateNonNumerical(classNum, featureCollection);
}
} catch (IllegalFilterException e) { // accepts exploded
LOGGER.log(Level.SEVERE, "EqualIntervalFunction calculate(SimpleFeatureCollection) failed", e);
return null;
} catch (IOException e) { // getResult().getValue() exploded
LOGGER.log(Level.SEVERE, "EqualIntervalFunction calculate(SimpleFeatureCollection) failed", e);
return null;
}
}
private RangedClassifier calculateNumerical(int classNum, Comparable globalMin, Comparable globalMax) {
double slotWidth = (((Number) globalMax).doubleValue() - ((Number) globalMin).doubleValue()) / classNum;
//size arrays
Comparable[] localMin = new Comparable[classNum];
Comparable[] localMax = new Comparable[classNum];
for (int i = 0; i < classNum; i++) {
//calculate the min + max values
localMin[i] = new Double(((Number) globalMin).doubleValue() + (i * slotWidth));
localMax[i] = new Double(((Number) globalMax).doubleValue() - ((classNum - i - 1) * slotWidth));
//determine number of decimal places to allow
int decPlaces = decimalPlaces(slotWidth);
//clean up truncation error
if (decPlaces > -1) {
localMin[i] = new Double(round(((Number) localMin[i]).doubleValue(), decPlaces));
localMax[i] = new Double(round(((Number) localMax[i]).doubleValue(), decPlaces));
}
if (i == 0) {
//ensure first min is less than or equal to globalMin
if (localMin[i].compareTo(new Double(((Number) globalMin).doubleValue())) < 0)
localMin[i] = new Double(fixRound(((Number) localMin[i]).doubleValue(), decPlaces, false));
} else if (i == classNum - 1) {
//ensure last max is greater than or equal to globalMax
if (localMax[i].compareTo(new Double(((Number) globalMax).doubleValue())) > 0)
localMax[i] = new Double(fixRound(((Number) localMax[i]).doubleValue(), decPlaces, true));
}
//synchronize min with previous max
if ((i != 0) && (!localMin[i].equals(localMax[i-1]))) {
localMin[i] = localMax[i-1];
}
}
return new RangedClassifier(localMin, localMax);
}
@SuppressWarnings("unchecked")
private RangedClassifier calculateNonNumerical(int classNum, FeatureCollection<?,?> featureCollection) throws IOException {
//obtain of list of unique values, so we can enumerate
UniqueVisitor uniqueVisit = new UniqueVisitor(getExpression());
featureCollection.accepts(uniqueVisit, new NullProgressListener());
List result = uniqueVisit.getResult().toList();
//sort the results and put them in an array
Collections.sort(result);
Comparable[] values = (Comparable[]) result.toArray(new Comparable[result.size()]);
//size arrays
Comparable[] localMin = new Comparable[classNum];
Comparable[] localMax = new Comparable[classNum];
//we have 2 options here:
//1. break apart by numeric value: (aaa, aab, aac, bbb) --> [aaa, aab, aac], [bbb]
//2. break apart by item count: --> [aaa, aab], [aac, bbb]
// this code currently implements option #2 (this is a quantile, why don't we use their code instead)
//calculate number of items to put in each of the larger bins
int binPop = new Double(Math.ceil((double) values.length / classNum)).intValue();
//determine index of bin where the next bin has one less item
int lastBigBin = values.length % classNum;
if (lastBigBin == 0) lastBigBin = classNum;
else lastBigBin--;
int itemIndex = 0;
//for each bin
for (int binIndex = 0; binIndex < classNum; binIndex++) {
//store min
if (binIndex < localMin.length)
localMin[binIndex] = (itemIndex < values.length ? values[itemIndex] : values[values.length-1]);
else
localMin[localMin.length-1] = (itemIndex < values.length ? values[itemIndex] : values[values.length-1]);
itemIndex+=binPop;
//store max
if (binIndex == classNum - 1) {
if (binIndex < localMax.length)
localMax[binIndex] = (itemIndex < values.length ? values[itemIndex] : values[values.length-1]);
else
localMax[localMax.length-1] = (itemIndex < values.length ? values[itemIndex] : values[values.length-1]);
} else {
if (binIndex < localMax.length)
localMax[binIndex] = (itemIndex+1 < values.length ? values[itemIndex+1] : values[values.length-1]);
else
localMax[localMax.length-1] = (itemIndex+1 < values.length ? values[itemIndex+1] : values[values.length-1]);
}
if (lastBigBin == binIndex)
binPop--; // decrease the number of items in a bin for the
// next iteration
}
return new RangedClassifier(localMin, localMax);
}
public RangedClassifier evaluate( Object object ) {
if (!(object instanceof FeatureCollection)) {
return null;
}
return calculate((SimpleFeatureCollection) object);
}
public int getArgCount() {
return 2;
}
}