/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* Copyright (C) 2007-2008-2009 GeoSolutions S.A.S.
* http://www.geo-solutions.it
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.sldservice.utils.classifier;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollection;
import org.geotools.filter.function.Classifier;
import org.geotools.filter.function.ExplicitClassifier;
import org.geotools.filter.function.RangedClassifier;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.styling.Mark;
import org.geotools.styling.Rule;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
/**
* Creates a List of Rule using different classification methods Sets up only
* filter not Symbolyzer Available Classification: Quantile,Unique Interval &
* Equal Interval
*
* @author kappu
*
*/
public class RulesBuilder {
private final static Logger LOGGER = Logger.getLogger(RulesBuilder.class.toString());
private FilterFactory2 ff;
private StyleFactory styleFactory;
private StyleBuilder sb;
public RulesBuilder() {
ff = CommonFactoryFinder.getFilterFactory2(GeoTools.getDefaultHints());
styleFactory = CommonFactoryFinder.getStyleFactory(GeoTools.getDefaultHints());
sb = new StyleBuilder();
}
/**
* Generate a List of rules using quantile classification Sets up only
* filter not symbolizer
*
* @param features
* @param property
* @param classNumber
*
*/
public List<Rule> quantileClassification(FeatureCollection features,
String property, Class<?> propertyType, int classNumber, boolean open, boolean normalize) {
FeatureType fType;
Classifier groups = null;
try {
final Function classify = ff.function("Quantile", ff.property(property),
ff.literal(classNumber));
groups = (Classifier) classify.evaluate(features);
if (groups instanceof RangedClassifier)
if(open)
return openRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else
return closedRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else if (groups instanceof ExplicitClassifier)
return this.explicitRules((ExplicitClassifier) groups, property, propertyType);
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build Quantile Classification"
+ e.getLocalizedMessage(), e);
}
return null;
}
/**
* Generate a List of rules using equal interval classification Sets up only
* filter not symbolizer
*
* @param features
* @param property
* @param classNumber
*
*/
public List<Rule> equalIntervalClassification(FeatureCollection features, String property,
Class<?> propertyType, int classNumber, boolean open, boolean normalize) {
Classifier groups = null;
try {
final Function classify = ff.function("EqualInterval", ff.property(property), ff.literal(classNumber));
groups = (Classifier) classify.evaluate(features);
//System.out.println(groups.getSize());
if (groups instanceof RangedClassifier)
if(open)
return openRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else
return closedRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else if (groups instanceof ExplicitClassifier)
return this.explicitRules((ExplicitClassifier) groups, property, propertyType);
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build EqualInterval Classification"
+ e.getLocalizedMessage(), e);
}
return null;
}
/**
* Generate a List of rules using unique interval classification Sets up
* only filter not symbolizer
*
* @param features
* @param property
*
*/
public List<Rule> uniqueIntervalClassification(FeatureCollection features,
String property, Class<?> propertyType, int intervals, boolean normalize) throws IllegalArgumentException {
Classifier groups = null;
int classNumber = features.size();
try {
final Function classify = ff.function("UniqueInterval", ff.property(property), ff.literal(classNumber));
groups = (Classifier) classify.evaluate(features);
if (groups instanceof RangedClassifier)
return this.closedRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else if (groups instanceof ExplicitClassifier) {
ExplicitClassifier explicitGroups = (ExplicitClassifier) groups;
if(intervals > 0 && explicitGroups.getSize() > intervals) {
throw new IllegalArgumentException("Intervals: " + explicitGroups.getSize());
}
return this.explicitRules(explicitGroups, property, propertyType);
}
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build UniqueInterval Classification"
+ e.getLocalizedMessage(), e);
if(e instanceof IllegalArgumentException) {
throw (IllegalArgumentException)e;
}
}
return null;
}
/**
* Generate Polygon Symbolyzer for each rule in list
* Fill color is choose from rampcolor
* @param rules
* @param fillRamp
* @param reverseColors
*/
public void polygonStyle(List<Rule> rules, ColorRamp fillRamp, boolean reverseColors) {
Iterator<Rule> it;
Rule rule;
Iterator<Color> colors;
Color color;
try {
//adjust the colorRamp with the correct number of classes
fillRamp.setNumClasses(rules.size());
if (reverseColors) {
fillRamp.revert();
}
colors = fillRamp.getRamp().iterator();
it = rules.iterator();
while (it.hasNext() && colors.hasNext()) {
color = colors.next();
rule = it.next();
rule.setSymbolizers(new Symbolizer[] { sb.createPolygonSymbolizer(sb.createStroke(Color.BLACK,1),sb.createFill(color)) });
}
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build polygon Symbolizer"
+ e.getLocalizedMessage(), e);
}
}
/**
* Generate Polygon Symbolyzer for each rule in list
* Fill color is choose from rampcolor
* @param rules
* @param ramp
*/
public void pointStyle(List<Rule> rules, ColorRamp fillRamp, boolean reverseColors) {
Iterator<Rule> it;
Rule rule;
Iterator<Color> colors;
Color color;
try {
//adjust the colorRamp with the correct number of classes
fillRamp.setNumClasses(rules.size());
if (reverseColors) {
fillRamp.revert();
}
colors = fillRamp.getRamp().iterator();
it = rules.iterator();
while (it.hasNext() && colors.hasNext()) {
color = colors.next();
rule = it.next();
//sb.createStroke(Color.BLACK,1),
Mark mark = sb.createMark(StyleBuilder.MARK_CIRCLE,
sb.createFill(color), sb.createStroke(color));
rule.setSymbolizers(new Symbolizer[] { sb.createPointSymbolizer(sb.createGraphic(null, mark, null)) });
}
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build polygon Symbolizer"
+ e.getLocalizedMessage(), e);
}
}
/**
* Generate Line Symbolyzer for each rule in list
* Stroke color is choose from rampcolor
* @param rules
* @param fillRamp
* @param reverseColors
*/
public void lineStyle(List<Rule> rules, ColorRamp fillRamp, boolean reverseColors) {
Iterator<Rule> it;
Rule rule;
Iterator<Color> colors;
Color color;
try {
//adjust the colorRamp with the correct number of classes
fillRamp.setNumClasses(rules.size());
if (reverseColors) {
fillRamp.revert();
}
colors = fillRamp.getRamp().iterator();
it = rules.iterator();
while (it.hasNext() && colors.hasNext()) {
color = colors.next();
rule = it.next();
rule.setSymbolizers(new Symbolizer[] { sb.createLineSymbolizer(color) });
}
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build Line Symbolizer"
+ e.getLocalizedMessage(), e);
}
}
public StyleFactory getStyleFactory() {
return this.styleFactory;
}
/**
* Generate Rules from Rangedclassifier groups
* build a List of rules
* @param groups
* @param property
*
*/
private List<Rule> openRangedRules(RangedClassifier groups, String property, Class<?> propertyType, boolean normalize) {
Rule r;
Filter f;
List<Rule> list = new ArrayList();
Expression att = normalizeProperty(ff.property(property), propertyType, normalize);
try {
/* First class */
r = styleFactory.createRule();
if(groups.getMin(0).equals(groups.getMax(0))){
f = ff.equals(att, ff.literal(groups.getMax(0)));
r.setFilter(f);
r.setTitle( ff.literal(groups.getMax(0)).toString());
list.add(r);
}else{
f = ff.lessOrEqual(att, ff.literal(groups.getMax(0)));
r.setFilter(f);
r.setTitle(" <= " + ff.literal(groups.getMax(0)));
list.add(r);
}
for (int i = 1; i < groups.getSize() - 1; i++) {
r = styleFactory.createRule();
if(groups.getMin(i).equals(groups.getMax(i))){
f = ff.equals(att, ff.literal(groups.getMax(i)));
r.setTitle( ff.literal(groups.getMin(i)).toString());
r.setFilter(f);
list.add(r);
}else{
f = ff.and(
ff.greater(att, ff.literal(groups.getMin(i))),
ff.lessOrEqual(att, ff.literal(groups.getMax(i)))
);
r.setTitle(" > " + ff.literal(groups.getMin(i)) + " AND <= " + ff.literal(groups.getMax(i)));
r.setFilter(f);
list.add(r);
}
}
/* Last class */
r = styleFactory.createRule();
if(groups.getMin(groups.getSize() - 1).equals(groups.getMax(groups.getSize() - 1))){
f = ff.equals(att, ff.literal(groups.getMin(groups.getSize() - 1)));
r.setFilter(f);
r.setTitle( ff.literal(groups.getMin(groups.getSize() - 1)).toString());
list.add(r);
}else{
f = ff.greater(att, ff.literal(groups.getMin(groups.getSize() - 1)));
r.setFilter(f);
r.setTitle(" > " + ff.literal(groups.getMin(groups.getSize() - 1)));
list.add(r);
}
return list;
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build Open Ranged rules"
+ e.getLocalizedMessage(), e);
}
return null;
}
private Expression normalizeProperty(PropertyName property,
Class<?> propertyType, boolean normalize) {
if(normalize && (Integer.class.isAssignableFrom(propertyType) || Long.class.isAssignableFrom(propertyType))) {
return ff.function("parseDouble", property);
}
return property;
}
/**
* Generate Rules from Rangedclassifier groups
* build a List of rules
* @param groups
* @param property
*
*/
private List<Rule> closedRangedRules(RangedClassifier groups, String property, Class<?> propertyType, boolean normalize) {
Rule r;
Filter f;
List<Rule> list = new ArrayList();
Expression att = normalizeProperty(ff.property(property), propertyType, normalize);
try {
/* First class */
r = styleFactory.createRule();
for (int i = 0; i < groups.getSize(); i++) {
r = styleFactory.createRule();
if(i > 0 && groups.getMax(i).equals(groups.getMax(i -1)))
continue;
if(groups.getMin(i).equals(groups.getMax(i))){
f = ff.equals(att, ff.literal(groups.getMin(i)));
r.setTitle( ff.literal(groups.getMin(i)).toString());
r.setFilter(f);
list.add(r);
} else {
f = ff.and(
i == 0 ? ff.greaterOrEqual(att, ff.literal(groups.getMin(i))) : ff.greater(att, ff.literal(groups.getMin(i))),
ff.lessOrEqual(att, ff.literal(groups.getMax(i)))
);
r.setTitle(" > " + ff.literal(groups.getMin(i)) + " AND <= " + ff.literal(groups.getMax(i)));
r.setFilter(f);
list.add(r);
}
}
return list;
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build closed Ranged Rules"
+ e.getLocalizedMessage(), e);
}
return null;
}
/**
* Generate Rules from Explicit classifier groups
* build a List of rules
* @param groups
* @param property
*
*/
private List<Rule> explicitRules(ExplicitClassifier groups, String property, Class<?> propertyType) {
Rule r;
Filter f;
List<Rule> list = new ArrayList();
PropertyName att = ff.property(property);
String szFilter = "";
String szTitle = "";
Literal val;
try {
for (int i = 0; i < groups.getSize(); i++) {
r = styleFactory.createRule();
Set ls = groups.getValues(i);
Iterator it = ls.iterator();
val = ff.literal(it.next());
szFilter = att + "=\'" + val + "\'";
szTitle = "" + val;
while (it.hasNext()) {
val = ff.literal(it.next());
szFilter += " OR " + att + "=\'" + val + "\'";
szTitle += " OR " + val;
}
f = CQL.toFilter(szFilter);
r.setTitle(szTitle);
r.setFilter(f);
list.add(r);
}
return list;
} catch (CQLException e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build explicit Rules"
+ e.getLocalizedMessage(), e);
}
return null;
}
/**
* Generate a List of rules using Jenks Natural Breaks classification Sets up only
* filter not symbolizer
*
* @param features
* @param property
* @param classNumber
*
*/
public List<Rule> jenksClassification(FeatureCollection features, String property, Class<?> propertyType, int classNumber, boolean open, boolean normalize) {
Classifier groups = null;
try {
final Function classify = ff.function("Jenks", ff.property(property), ff.literal(classNumber));
groups = (Classifier) classify.evaluate(features);
//System.out.println(groups.getSize());
if (groups instanceof RangedClassifier)
if(open)
return openRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else
return closedRangedRules((RangedClassifier) groups, property, propertyType, normalize);
else if (groups instanceof ExplicitClassifier)
return this.explicitRules((ExplicitClassifier) groups, property, propertyType);
} catch (Exception e) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.log(Level.INFO, "Failed to build Jenks classification"
+ e.getLocalizedMessage(), e);
}
return null;
}
}