/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2009, Johann Sorel
*
* 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.geotoolkit.style.category;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.sis.internal.feature.AttributeConvention;
import org.geotoolkit.data.FeatureStoreRuntimeException;
import org.geotoolkit.data.FeatureIterator;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.factory.Factory;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.map.FeatureMapLayer;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.style.MutableFeatureTypeStyle;
import org.geotoolkit.style.MutableRule;
import org.geotoolkit.style.MutableStyleFactory;
import org.geotoolkit.style.StyleConstants;
import org.geotoolkit.style.interval.RandomPalette;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.Operation;
import org.opengis.feature.PropertyNotFoundException;
import org.opengis.feature.PropertyType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.style.Fill;
import org.opengis.style.Graphic;
import org.opengis.style.GraphicalSymbol;
import org.opengis.style.LineSymbolizer;
import org.opengis.style.Mark;
import org.opengis.style.PointSymbolizer;
import org.opengis.style.PolygonSymbolizer;
import org.opengis.style.Rule;
import org.opengis.style.Stroke;
import org.opengis.style.Symbolizer;
/**
* Random style factory. This is a convini class if you dont need special styles.
* This class will provide you simple et good looking styles for your maps.
*
* @author Johann Sorel (Puzzle-GIS)
* @module
*/
public class CategoryStyleBuilder extends Factory {
private final MutableStyleFactory sf;
private final FilterFactory ff;
private final MutableFeatureTypeStyle fts;
private final List<PropertyName> properties = new ArrayList<PropertyName>();
private Class<? extends Symbolizer> expectedType = null;
private boolean other = false;
private PropertyName currentProperty = null;
private FeatureMapLayer layer;
private Symbolizer template;
private RandomPalette palette;
public CategoryStyleBuilder() {
this(null,null);
}
public CategoryStyleBuilder(final MutableStyleFactory styleFactory, final FilterFactory filterFactory){
if(styleFactory == null){
sf = (MutableStyleFactory) FactoryFinder.getStyleFactory(new Hints(Hints.STYLE_FACTORY, MutableStyleFactory.class));
}else{
sf = styleFactory;
}
if(filterFactory == null){
ff = FactoryFinder.getFilterFactory(null);
}else{
ff = filterFactory;
}
fts = sf.featureTypeStyle();
}
public void analyze(final FeatureMapLayer layer){
this.layer = layer;
fts.rules().clear();
properties.clear();
if(layer != null){
FeatureType schema = layer.getCollection().getFeatureType();
for(PropertyType desc : schema.getProperties(true)){
if(desc instanceof AttributeType){
Class<?> type = ((AttributeType)desc).getValueClass();
if(!Geometry.class.isAssignableFrom(type)){
properties.add(ff.property(desc.getName().tip().toString()));
}
}
}
//find the geometry class for template
Class<?> geoClass = null;
try{
PropertyType geo = schema.getProperty(AttributeConvention.GEOMETRY_PROPERTY.toString());
geoClass = ((AttributeType)((Operation)geo).getResult()).getValueClass();
}catch(PropertyNotFoundException ex){
}
if(geoClass==null){
return;
}
if(Polygon.class.isAssignableFrom(geoClass) || MultiPolygon.class.isAssignableFrom(geoClass)){
Stroke stroke = sf.stroke(Color.BLACK, 1);
Fill fill = sf.fill(Color.BLUE);
template = sf.polygonSymbolizer(stroke,fill,null);
expectedType = PolygonSymbolizer.class;
}else if(LineString.class.isAssignableFrom(geoClass) || MultiLineString.class.isAssignableFrom(geoClass)){
Stroke stroke = sf.stroke(Color.BLUE, 2);
template = sf.lineSymbolizer(stroke,null);
expectedType = LineSymbolizer.class;
}else{
Stroke stroke = sf.stroke(Color.BLACK, 1);
Fill fill = sf.fill(Color.BLUE);
List<GraphicalSymbol> symbols = new ArrayList<GraphicalSymbol>();
symbols.add(sf.mark(StyleConstants.MARK_CIRCLE, fill, stroke));
Graphic gra = sf.graphic(symbols, ff.literal(1), ff.literal(12), ff.literal(0), sf.anchorPoint(), sf.displacement());
template = sf.pointSymbolizer(gra, null);
expectedType = PointSymbolizer.class;
}
//try to rebuild the previous analyze if it was one
List<MutableFeatureTypeStyle> ftss = layer.getStyle().featureTypeStyles();
if(ftss.size() == 1){
MutableFeatureTypeStyle fts = ftss.get(0);
//defensive copy avoid synchronization
List<MutableRule> candidateRules = new ArrayList<MutableRule>(fts.rules());
for(Rule r : candidateRules){
//defensive copy avoid synchronization
List<? extends Symbolizer> candidateSymbols = new ArrayList<Symbolizer>(r.symbolizers());
if(candidateSymbols.size() != 1) break;
Symbolizer symbol = candidateSymbols.get(0);
if(expectedType.isInstance(symbol)){
if(r.isElseFilter()){
//it looks like it's a valid classification "other" rule
this.fts.rules().add((MutableRule) r);
template = symbol;
other = true;
}else{
Filter f = r.getFilter();
if(f != null && f instanceof PropertyIsEqualTo){
PropertyIsEqualTo equal = (PropertyIsEqualTo) f;
Expression exp1 = equal.getExpression1();
Expression exp2 = equal.getExpression2();
if(exp1 instanceof PropertyName && exp2 instanceof Literal){
if(properties.contains(exp1)){
//it looks like it's a valid classification property rule
this.fts.rules().add((MutableRule) r);
template = symbol;
currentProperty = (PropertyName) exp1;
}else{
//property is not in the schema
break;
}
}else if(exp2 instanceof PropertyName && exp1 instanceof Literal){
if(properties.contains(exp2)){
//it looks like it's a valid classification property rule
this.fts.rules().add((MutableRule) r);
template = symbol;
currentProperty = (PropertyName) exp2;
}else{
//property is not in the schema
break;
}
}else{
//mismatch analyze structure
break;
}
}
}
}else{
break;
}
}
}
}
}
public Symbolizer getTemplate() {
return template;
}
public void setTemplate(final Symbolizer template) {
this.template = template;
}
public RandomPalette getPalette() {
return palette;
}
public void setPalette(final RandomPalette palette) {
this.palette = palette;
}
public MutableFeatureTypeStyle getFeatureTypeStyle() {
return fts;
}
public List<PropertyName> getProperties() {
return properties;
}
public void setCurrentProperty(final PropertyName currentProperty) {
this.currentProperty = currentProperty;
}
public PropertyName getCurrentProperty() {
return currentProperty;
}
public boolean isOther() {
return other;
}
public void setOther(final boolean other) {
this.other = other;
}
public List<MutableRule> create(){
//search the different values
final Set<Object> differentValues = new HashSet<Object>();
final PropertyName property = currentProperty;
final QueryBuilder builder = new QueryBuilder();
builder.setTypeName(layer.getCollection().getFeatureType().getName());
builder.setProperties(new String[]{property.getPropertyName()});
final Query query = builder.buildQuery();
FeatureIterator features = null;
try{
features = layer.getCollection().subCollection(query).iterator();
while(features.hasNext()){
final Feature feature = features.next();
differentValues.add(property.evaluate(feature));
}
}catch(DataStoreException ex){
ex.printStackTrace();
}catch(FeatureStoreRuntimeException ex){
ex.printStackTrace();
}finally{
if(features != null){
features.close();
}
}
//generate the different rules
fts.rules().clear();
for(Object obj : differentValues){
fts.rules().add(createRule(property, obj));
}
//generate the other rule if asked
if(other){
MutableRule r = sf.rule(createSymbolizer());
r.setElseFilter(true);
r.setDescription(sf.description("other", "other"));
fts.rules().add(r);
}
return fts.rules();
}
public Symbolizer createSymbolizer(){
return derivateSymbolizer(template, palette.next());
}
/**
* Derivate a symbolizer with a new color.
*/
public Symbolizer derivateSymbolizer(final Symbolizer symbol, final Color color){
if(symbol instanceof PolygonSymbolizer){
PolygonSymbolizer ps = (PolygonSymbolizer)symbol;
Fill fill = sf.fill(sf.literal(color),ps.getFill().getOpacity());
return sf.polygonSymbolizer(ps.getName(), ps.getGeometryPropertyName(),
ps.getDescription(), ps.getUnitOfMeasure(),
ps.getStroke(),fill,ps.getDisplacement(),ps.getPerpendicularOffset());
}else if(symbol instanceof LineSymbolizer){
LineSymbolizer ls = (LineSymbolizer) symbol;
Stroke oldStroke = ls.getStroke();
Stroke stroke = sf.stroke(sf.literal(color),oldStroke.getOpacity(),oldStroke.getWidth(),
oldStroke.getLineJoin(),oldStroke.getLineCap(),oldStroke.getDashArray(),oldStroke.getDashOffset());
return sf.lineSymbolizer(ls.getName(), ls.getGeometryPropertyName(),
ls.getDescription(), ls.getUnitOfMeasure(), stroke, ls.getPerpendicularOffset());
}else if(symbol instanceof PointSymbolizer){
PointSymbolizer ps = (PointSymbolizer) symbol;
Graphic oldGraphic = ps.getGraphic();
Mark oldMark = (Mark) oldGraphic.graphicalSymbols().get(0);
Fill fill = sf.fill(sf.literal(color),oldMark.getFill().getOpacity());
List<GraphicalSymbol> symbols = new ArrayList<GraphicalSymbol>();
symbols.add(sf.mark(oldMark.getWellKnownName(), fill, oldMark.getStroke()));
Graphic graphic = sf.graphic(symbols, oldGraphic.getOpacity(),oldGraphic.getSize(),
oldGraphic.getRotation(),oldGraphic.getAnchorPoint(),oldGraphic.getDisplacement());
return sf.pointSymbolizer(graphic,ps.getGeometryPropertyName());
}else{
throw new IllegalArgumentException("unexpected symbolizer type : " + symbol);
}
}
public MutableRule createRule(final PropertyName property, final Object obj){
MutableRule r = sf.rule(createSymbolizer());
r.setFilter(ff.equals(property, ff.literal(obj)));
r.setDescription(sf.description(obj.toString(), obj.toString()));
return r;
}
}