/*******************************************************************************
* Copyright (c) 2010 Stefan A. Tzeggai.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Stefan A. Tzeggai - initial API and implementation
******************************************************************************/
package org.geopublishing.atlasStyler.rulesLists;
import java.awt.Color;
import java.util.Collection;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.geopublishing.atlasStyler.ASUtil;
import org.geopublishing.atlasStyler.RuleChangeListener;
import org.geopublishing.atlasStyler.RuleChangedEvent;
import org.geotools.filter.AndImpl;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Rule;
import org.geotools.styling.Symbolizer;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.PropertyIsBetween;
import de.schmitzm.geotools.FilterUtil;
import de.schmitzm.geotools.GTUtil;
import de.schmitzm.geotools.feature.FeatureUtil;
import de.schmitzm.geotools.feature.FeatureUtil.GeometryForm;
import de.schmitzm.geotools.styling.StyledFeaturesInterface;
import de.schmitzm.geotools.styling.StyledLayerUtil;
public abstract class FeatureRuleList extends AbstractRulesList {
private static final Logger LOGGER = Logger
.getLogger(FeatureRuleList.class);
/**
* The children of this class define most metainfo. Some metainformation is
* the same for all children and is added here.
*
* @param metaInfoString
* The metaInfoString starting with a getType()-result and some
* KVPs
*
* @return an expanded {@link String} with more KVPs
*
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
@Override
public String extendMetaInfoString() {
String metaInfoString = super.extendMetaInfoString();
metaInfoString += METAINFO_SEPERATOR_CHAR + KVP_VALUE_FIELD
+ METAINFO_KVP_EQUALS_CHAR + getValue_field_name();
metaInfoString += METAINFO_SEPERATOR_CHAR + KVP_NORMALIZATION_FIELD
+ METAINFO_KVP_EQUALS_CHAR + getNormalizer_field_name();
return metaInfoString;
}
/**
* @param filter
* A {@link Filter}
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*
* @return <code>null</code> if it is not a "BetweenFilter"
*/
public static double[] interpretBetweenFilter(Filter filter) {
if (filter instanceof AndImpl) {
// This is a AND ( NOT ( NODATA ) , BETWEENFILTER) construction
// We continue the interpretion with only the last filter
Iterator<Filter> fi = ((AndImpl) filter).getFilterIterator();
while (fi.hasNext())
filter = fi.next();
}
if (filter instanceof PropertyIsBetween) {
PropertyIsBetween betweenFilter = (PropertyIsBetween) filter;
double lower = Double.parseDouble(betweenFilter.getLowerBoundary()
.toString());
double upper = Double.parseDouble(betweenFilter.getUpperBoundary()
.toString());
return new double[] { lower, upper };
}
throw new RuntimeException("Unparsable Filter " + filter);
}
/**
* The children of this class parse most metainfo. Some metainformation is
* the same for all children and is parsed here. This method must be called
* from all children.
*
* @param metaInfoString
* The metaInfoString cleared from with a getType()-result and
* some KVPs
*
* @return an expanded {@link String} with more KVPs
*
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
protected void parseMetaInfoString(String metaInfoString) {
/***********************************************************************
* Parsing a list of Key-Value Pairs from the FeatureTypeStyleName
*/
String[] kvp = new String[] { "uninitialized", "uninit" };
String value = "nothging yet";
try {
String[] params = metaInfoString.split(METAINFO_SEPERATOR_CHAR);
for (String p : params) {
kvp = p.split(METAINFO_KVP_EQUALS_CHAR);
if (kvp[0].equalsIgnoreCase(KVP_NORMALIZATION_FIELD.toString())) {
value = kvp[1];
if (value.equalsIgnoreCase("null"))
setNormalizer_field_name(null);
else
setNormalizer_field_name(FeatureUtil
.findBestMatchingAttribute(
getStyledFeatures().getSchema(), value)
.getLocalPart());
}
if (kvp[0].equalsIgnoreCase(KVP_VALUE_FIELD.toString())) {
value = kvp[1];
if (value.equalsIgnoreCase("null"))
setValue_field_name(null);
else
setValue_field_name(FeatureUtil
.findBestMatchingAttributeFallBackFirstNumeric(
getStyledFeatures().getSchema(), kvp[1])
.getLocalPart());
}
}
} catch (RuntimeException e) {
LOGGER.error("KVP=" + kvp[0] + kvp[1]);
LOGGER.error("VALUE=" + value);
throw (e);
}
}
/**
* @return the normalizer_field_name
*/
public String getNormalizer_field_name() {
return normalizer_field_name;
}
/**
* @param normalizer_field_name
* the normalizer_field_name to set
*/
final public void setNormalizer_field_name(String normalizer_field_name) {
this.normalizer_field_name = normalizer_field_name;
}
/**
* @param value_field_name
* the value_field_name to set
*/
public void setValue_field_name(String value_field_name) {
this.value_field_name = value_field_name;
}
/**
* The {@link String} name of the attribute which contains the quantity
* values
*/
private String value_field_name;
/**
* The {@link String} name of the attribute used to normalize the quantity
* attribute
*/
private String normalizer_field_name;
/**
* @return the value_field_name
*/
final public String getValue_field_name() {
return value_field_name;
}
/** KEY-name for the KVPs in the meta information * */
protected static final String KVP_NORMALIZATION_FIELD = "NORM";
/** KEY-name for the KVPs in the meta information * */
private static final String KVP_VALUE_FIELD = "VALUE";
/**
* When importing {@link Rule}s, rules with this name are interpreted as the
* "NODATA" rules. Usually there is only one.
*/
public static final String NODATA_RULE_NAME = "NODATA_RULE";
/**
* When importing {@link Rule}s, rules with this name are interpreted as the
* "NODATA" rule. If running in Atlas/GP mode, this rule will NOT appear in
* the legend.
*/
public static final String NODATA_RULE_NAME_HIDEINLEGEND = NODATA_RULE_NAME
+ "_" + StyledLayerUtil.HIDE_IN_LAYER_LEGEND_HINT;
/**
* When importing {@link Rule}s, rules with this name are interpreted as the
* "NODATA" rule. If running in Atlas/GP mode, this rule will appear in the
* legend.
*/
public static final String NODATA_RULE_NAME_SHOWINLEGEND = NODATA_RULE_NAME;
protected FilterFactory2 ff2 = FilterUtil.FILTER_FAC2;
/**
* This {@link RuleChangeListener} is added to the template in
* {@link #getNoDataSymbol()} and will propagate any template changes to
* this rule list.
*/
private final RuleChangeListener listenToNoDataRLChangesAndPropageToFeatureRL = new RuleChangeListener() {
@Override
public void changed(RuleChangedEvent e) {
FeatureRuleList.this.fireEvents(new RuleChangedEvent(
"nodata symbology changed", FeatureRuleList.this));
}
};
private SingleRuleList<? extends Symbolizer> noDataSymbol = null;
final private StyledFeaturesInterface<?> styledFeatures;
private SingleRuleList<? extends Symbolizer> template;
public FeatureRuleList(RulesListType rulesListType,
StyledFeaturesInterface<?> styledFeatures,
GeometryForm geometryForm, boolean withDefaults) {
super(rulesListType, geometryForm);
this.styledFeatures = styledFeatures;
// Initialize value_field_name if "withDefaults"
Collection<String> numericalFieldNames = FeatureUtil
.getNumericalFieldNames(getStyledFeatures().getSchema(), false);
if (numericalFieldNames.size() > 0)
value_field_name = numericalFieldNames.toArray(new String[] {})[0];
}
// abstract public SingleRuleList<? extends Symbolizer>
// getDefaultTemplate();
public SingleRuleList<? extends Symbolizer> getDefaultTemplate() {
return ASUtil.getDefaultTemplate(getGeometryForm());
}
/***************************************************************************
* @return Returns the SLD {@link FeatureTypeStyle}s that represents this
* RuleList. This method implemented here does set the
* FeatureTypeName in the {@link FeatureTypeStyle}.
*/
@Override
public FeatureTypeStyle getFTS() {
FeatureTypeStyle fts = super.getFTS();
fts.featureTypeNames().add(styledFeatures.getSchema().getName());
return fts;
}
/**
* Return the {@link Filter} that will catch all NODATA values
*/
abstract public Filter getNoDataFilter();
/**
* Return a {@link SingleRuleList} that shall be used to paint all NODATA
* values. If <code>null</code>, then all features matching the
* {@link #getNoDataFilter()} shall not be painted at all.
*/
public SingleRuleList<? extends Symbolizer> getNoDataSymbol() {
if (noDataSymbol == null) {
noDataSymbol = ASUtil.getDefaultNoDataSymbol(getGeometryForm());
}
noDataSymbol.addListener(listenToNoDataRLChangesAndPropageToFeatureRL);
return noDataSymbol;
}
public StyledFeaturesInterface<?> getStyledFeatures() {
return styledFeatures;
}
/***************************************************************************
* TEMPLATE STUFF
*/
public SingleRuleList<? extends Symbolizer> getTemplate() {
if (template == null)
return getDefaultTemplate();
return template;
}
/**
* Uses the Symbolizers of the {@link Rule} to create a NoDataSymbol of type
* {@link SingleRuleList}. The label for NODATA is stored in the rule's
* title field.<br/>
* This does not import any NODATA values. In Geopublisher the NODATA values
* are stored in the atlas.xml. But it would maybe be a good idea to import
* them here?!
*/
public void importNoDataRule(Rule r) {
getNoDataSymbol().getSymbolizers().clear();
getNoDataSymbol().addSymbolizers(r.symbolizers());
getNoDataSymbol().setLabel(GTUtil.descriptionTitle(r.getDescription()));
getNoDataSymbol().setTitle("NODATARULE");
if (r.getName().toString()
.equals(FeatureRuleList.NODATA_RULE_NAME_HIDEINLEGEND)) {
getNoDataSymbol().setVisibleInLegend(false);
}
// Stub on how to parse the NODAT values...
// Filter filter = parseAbstractRlSettings(r.getFilter());
// Or ors = (Or) filter;
// for (Filter ndf : ors.getChildren()) {
// if (ndf instanceof IsNullImpl) {
// }
// }
}
/***************************************************************************
* ABSTRACT METHODS BEGIN HERE
*
* @return
**************************************************************************/
abstract public void importTemplate(FeatureTypeStyle importFTS);
/**
* Define how to draw NODATA values (null, NaN, Inf) by setting a Color and
* Opacity that will be used {@link SingleRuleList}
*/
public void setNoDataSymbol(Color color, double opacity) {
if (color == null)
throw new IllegalArgumentException("Color may not be null!");
// getNoDataSymbol().getSymbolizers().clear();
// Symbolizer symbolizer = getGeometryForm()
// Style style = FeatureUtil.createDefaultStyle(getGeometryForm());
// Symbolizer symbolizer =
// style.featureTypeStyles().get(0).rules().get(0).symbolizers().get(0);
SingleRuleList<? extends Symbolizer> rl = ASUtil
.getDefaultNoDataSymbol(getGeometryForm(), opacity, color,
color);
// rl.copyTo(getNoDataSymbol());
noDataSymbol = rl;
}
/**
* Define how to draw NODATA values (null, NaN, Inf) by setting a
* {@link SingleRuleList}
*/
public void setNoDataSymbol(
SingleRuleList<? extends Symbolizer> noDataRuleList) {
if (noDataRuleList == null) {
noDataSymbol = null;
} else {
// Maybe better: noDataSymbol = noDataRuleList; ?
noDataRuleList.copyTo(getNoDataSymbol());
}
}
/**
* Define how to draw NODATA values (null, NaN, Inf) by setting a single
* {@link Symbolizer}
*/
public void setNoDataSymbol(Symbolizer noDataSymbolizer) {
if (noDataSymbolizer == null) {
noDataSymbol = null;
} else {
// Maybe better: noDataSymbol = noDataRuleList; ?
getNoDataSymbol().getSymbolizers().clear();
getNoDataSymbol().addSymbolizer(noDataSymbolizer);
}
}
/**
* Sets a template Symbol used for this color graduation
*
* @param template
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
public void setTemplate(SingleRuleList<? extends Symbolizer> template) {
this.template = template;
fireEvents(new RuleChangedEvent("Set template", this));
}
}