001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.filter.function;
017:
018: import java.io.IOException;
019: import java.util.HashSet;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Set;
023: import java.util.logging.Level;
024:
025: import org.geotools.feature.FeatureCollection;
026: import org.geotools.feature.visitor.CalcResult;
027: import org.geotools.feature.visitor.QuantileListVisitor;
028: import org.geotools.util.NullProgressListener;
029:
030: /**
031: * Breaks a FeatureCollection into classes with an equal number of items in each.
032: *
033: * @author Cory Horner, Refractions Research Inc.
034: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/filter/function/QuantileFunction.java $
035: */
036: public class QuantileFunction extends ClassificationFunction {
037:
038: public QuantileFunction() {
039: setName("Quantile");
040: }
041:
042: public int getArgCount() {
043: return 2;
044: }
045:
046: private Object calculate(FeatureCollection featureCollection) {
047: // use a visitor to find the values in each bin
048: QuantileListVisitor quantileVisit = new QuantileListVisitor(
049: getExpression(), getClasses());
050: if (progress == null)
051: progress = new NullProgressListener();
052: try {
053: featureCollection.accepts(quantileVisit, progress);
054: } catch (IOException e) {
055: LOGGER
056: .log(
057: Level.SEVERE,
058: "QuantileFunction calculate(FeatureCollection) failed",
059: e);
060: return null;
061: }
062: if (progress.isCanceled())
063: return null;
064: CalcResult calcResult = quantileVisit.getResult();
065: if (calcResult == null)
066: return null;
067: List[] bin = (List[]) calcResult.getValue();
068:
069: //generate the min and max values, and round off if applicable/necessary
070: Comparable globalMin = (Comparable) bin[0].toArray()[0];
071: Object lastBin[] = bin[bin.length - 1].toArray();
072: if (lastBin.length == 0) {
073: return null;
074: }
075: Comparable globalMax = (Comparable) lastBin[lastBin.length - 1];
076:
077: if ((globalMin instanceof Number)
078: && (globalMax instanceof Number)) {
079: return calculateNumerical(bin, globalMin, globalMax);
080: } else {
081: return calculateNonNumerical(bin);
082: }
083: }
084:
085: private Object calculateNumerical(List[] bin, Comparable globalMin,
086: Comparable globalMax) {
087: int classNum = bin.length;
088: //size arrays
089: Comparable[] localMin = new Comparable[classNum];
090: Comparable[] localMax = new Comparable[classNum];
091: //globally consistent
092: //double slotWidth = (((Number) globalMax).doubleValue() - ((Number) globalMin).doubleValue()) / classNum;
093: for (int i = 0; i < classNum; i++) {
094: //copy the min + max values
095: List this Bin = bin[i];
096: localMin[i] = (Comparable) this Bin.get(0);
097: localMax[i] = (Comparable) this Bin.get(this Bin.size() - 1);
098: //locally accurate
099: double slotWidth = ((Number) localMax[i]).doubleValue()
100: - ((Number) localMin[i]).doubleValue();
101: if (slotWidth == 0.0) { //use global value, as there is only 1 value in this set
102: slotWidth = (((Number) globalMax).doubleValue() - ((Number) globalMin)
103: .doubleValue())
104: / classNum;
105: }
106: //determine number of decimal places to allow
107: int decPlaces = decimalPlaces(slotWidth);
108: decPlaces = Math
109: .max(decPlaces,
110: decimalPlaces(((Number) localMin[i])
111: .doubleValue()));
112: decPlaces = Math
113: .max(decPlaces,
114: decimalPlaces(((Number) localMax[i])
115: .doubleValue()));
116: //clean up truncation error
117: if (decPlaces > -1) {
118: localMin[i] = new Double(round(((Number) localMin[i])
119: .doubleValue(), decPlaces));
120: localMax[i] = new Double(round(((Number) localMax[i])
121: .doubleValue(), decPlaces));
122: }
123:
124: if (i == 0) {
125: //ensure first min is less than or equal to globalMin
126: if (localMin[i].compareTo(new Double(
127: ((Number) globalMin).doubleValue())) > 0)
128: localMin[i] = new Double(fixRound(
129: ((Number) localMin[i]).doubleValue(),
130: decPlaces, false));
131: } else if (i == classNum - 1) {
132: //ensure last max is greater than or equal to globalMax
133: if (localMax[i].compareTo(new Double(
134: ((Number) globalMax).doubleValue())) < 0)
135: localMax[i] = new Double(fixRound(
136: ((Number) localMax[i]).doubleValue(),
137: decPlaces, true));
138: }
139: //synchronize min with previous max
140: if ((i != 0) && (!localMin[i].equals(localMax[i - 1]))) {
141: if (!localMin[i].equals(localMax[i])) //only if the range contains more than 1 value
142: localMin[i] = localMax[i - 1];
143: }
144: }
145: //TODO: disallow having 2 identical bins (ie 0..0, 0..0, 0..0, 0..100)
146: return new RangedClassifier(localMin, localMax);
147: }
148:
149: private Object calculateNonNumerical(List[] bin) {
150: int classNum = bin.length;
151: //it's a string.. leave it be (just copy the values)
152: Set[] values = new Set[classNum];
153: for (int i = 0; i < classNum; i++) {
154: values[i] = new HashSet();
155: Iterator iterator = bin[i].iterator();
156: while (iterator.hasNext()) {
157: values[i].add(iterator.next());
158: }
159: }
160: return new ExplicitClassifier(values);
161: // alternative for ranged classifier
162: // localMin[i] = (Comparable) thisBin.get(0);
163: // localMax[i] = (Comparable) thisBin.get(thisBin.size()-1);
164: }
165:
166: public Object evaluate(Object feature) {
167: if (!(feature instanceof FeatureCollection)) {
168: return null;
169: }
170: return calculate((FeatureCollection) feature);
171: }
172:
173: }
|