/******************************************************************************* * 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.ArrayList; import java.util.List; import java.util.TreeSet; import org.apache.log4j.Logger; import org.geopublishing.atlasStyler.ASUtil; import org.geopublishing.atlasStyler.QuantitiesRuleList; import org.geopublishing.atlasStyler.RuleChangeListener; import org.geopublishing.atlasStyler.RuleChangedEvent; import org.geopublishing.atlasStyler.classification.CLASSIFICATION_METHOD; import org.geotools.brewer.color.BrewerPalette; import org.geotools.brewer.color.PaletteType; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Rule; import org.geotools.styling.Symbolizer; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import de.schmitzm.geotools.FilterUtil; import de.schmitzm.geotools.GTUtil; import de.schmitzm.geotools.data.amd.AttributeMetadataImpl; import de.schmitzm.geotools.feature.FeatureUtil.GeometryForm; import de.schmitzm.geotools.styling.StyledFeaturesInterface; import de.schmitzm.geotools.styling.StylingUtil; /** * * This abstract class represents a list of {@link Rule Rules} meant to style a * quantity with graduating colors. * * @author Stefan A. Tzeggai * */ public abstract class GraduatedColorRuleList extends QuantitiesRuleList { private static final Logger LOGGER = Logger .getLogger(GraduatedColorRuleList.class); /** * The {@link BrewerPalette} used in this {@link QuantitiesRuleList} */ private BrewerPalette brewerPalette = ASUtil.getPalettes(new PaletteType( true, false), -1)[0]; /** * This {@link RuleChangeListener} is added to the template in * {@link #getTemplate()} and will propagate any template changes to this * {@link GraduatedColorRuleList} */ private final RuleChangeListener listenToTemplateRLChangesAndPropageToGraduateColorsRL = new RuleChangeListener() { @Override public void changed(RuleChangedEvent e) { GraduatedColorRuleList.this.fireEvents(new RuleChangedEvent( "template changed", GraduatedColorRuleList.this)); } }; public GraduatedColorRuleList(RulesListType rulesListType, StyledFeaturesInterface<?> styledFeatures, GeometryForm geometryForm, boolean withDefaults) { super(rulesListType, styledFeatures, geometryForm, withDefaults); } /** * Together with {@link #parseMetaInfoString(String, FeatureTypeStyle)} this * allows loading and saving the RL */ @Override public String extendMetaInfoString() { String metaInfoString = super.extendMetaInfoString(); metaInfoString += METAINFO_SEPERATOR_CHAR + KVP_METHOD + METAINFO_KVP_EQUALS_CHAR + getMethod(); metaInfoString += METAINFO_SEPERATOR_CHAR + KVP_PALTETTE + METAINFO_KVP_EQUALS_CHAR + getBrewerPalette().getName(); // LOGGER.debug("metainfo= " + metaInfoString); return metaInfoString; } public BrewerPalette getBrewerPalette() { /** * For whatever reason, the brewerPalette.getPaletteSuitability() can be * greater than brewerPalette.getMaxColors(). So we try that first. */ int maxColors = brewerPalette.getPaletteSuitability() != null ? brewerPalette .getPaletteSuitability().getMaxColors() : brewerPalette .getMaxColors(); if (getNumClasses() > maxColors) { LOGGER.info("Reducing the Number of classes from " + getNumClasses() + " to " + maxColors + " because we don't have a betterPalette"); } return brewerPalette; } /** * @return An Array of Colors for the classes. The length of the array * equals the number of classes. */ @Override public Color[] getColors() { if (super.getColors() == null) { if (getNumClasses() > getBrewerPalette().getMaxColors()) { throw new RuntimeException(" numClasses (" + getNumClasses() + ") > getBrewerPalette().getMaxColors() (" + getBrewerPalette().getMaxColors() + ")"); } setColors(getBrewerPalette().getColors(getNumClasses())); } return super.getColors(); } /** * Return the {@link Filter} that will catch all NODATA values. */ @Override public Filter getNoDataFilter() { // Checking the value attribute for NODATA values String attributeLocalName = getValue_field_name(); AttributeMetadataImpl amd1 = getStyledFeatures() .getAttributeMetaDataMap().get(attributeLocalName); List<Filter> ors = new ArrayList<Filter>(); ors.add(ff2.isNull(ff2.property(attributeLocalName))); if (amd1 != null && amd1.getNodataValues() != null) { for (Object ndValue : amd1.getNodataValues()) { ors.add(ff2.equals(ff2.property(attributeLocalName), ff2.literal(ndValue))); } } // Checking the normalization attribute for NODATA values String normalizerLocalName = getNormalizer_field_name(); if (normalizerLocalName != null) { AttributeMetadataImpl amd2 = getStyledFeatures() .getAttributeMetaDataMap().get(normalizerLocalName); ors.add(ff2.isNull(ff2.property(normalizerLocalName))); // As we are dividing by this value, always add the zero also! ors.add(ff2.equals(ff2.property(normalizerLocalName), ff2.literal(0))); if (amd2 != null && amd2.getNodataValues() != null) for (Object ndValue : amd2.getNodataValues()) { ors.add(ff2.equals(ff2.property(normalizerLocalName), ff2.literal(ndValue))); } } Filter filter = FilterUtil.correctOrForValidation(ff2.or(ors)); // The NODATA rule also need to be enabled/disabled accoring to the // general stat eof the RuleList filter = addAbstractRlSettings(filter); return filter; } @Override public List<Rule> getRules() { ArrayList<Double> classLimitsAsArray = getClassLimitsAsArrayList(); if (classLimitsAsArray.size() == 1) { // Special case classLimitsAsArray.add(classLimitsAsArray.get(0)); } if (classLimitsAsArray.size() == 0) { return new ArrayList<Rule>(); } ArrayList<Rule> rules = new ArrayList<Rule>(); Literal lowerRange = null; Literal upperRange = null; Filter filter = null; /** * One Rule for every Class: */ for (int i = 0; i < getNumClasses(); i++) { Rule rule; Expression value; if (getNormalizer_field_name() == null) { value = ff2.property(getValue_field_name()); lowerRange = ff2.literal(classLimitsAsArray.get(i)); upperRange = ff2.literal(classLimitsAsArray.get(i + 1)); } else { value = ff2.divide(ff2.property(getValue_field_name()), ff2.property(getNormalizer_field_name())); // If we have a normalizing field, we always go for double lowerRange = ff2.literal(classLimitsAsArray.get(i)); upperRange = ff2.literal(classLimitsAsArray.get(i + 1)); } if (lowerRange.equals(upperRange)) { // If Upper and Lower are the same, use a PropertyEquals Filter // instead filter = ff2.equals(value, lowerRange); } else { // Normally use a between filter filter = ff2.between(value, lowerRange, upperRange); } SingleRuleList<? extends Symbolizer> clone = getTemplate().copy(); final Color newColor = getColors()[i]; clone.setColor(newColor); rule = clone.getRule(); // Exclude the NODATA values for the rule if (getNoDataFilter() != Filter.EXCLUDE) filter = ff2.and(ff2.not(getNoDataFilter()), filter); // Add the general on/off switch as the last AND filter filter = addAbstractRlSettings(filter); rule.setFilter(filter); rule.setTitle(getRuleTitles().get(i)); rule.setName("AS: " + (i + 1) + "/" + (getClassLimits().size() - 1) + " " + this.getClass().getSimpleName()); rules.add(rule); } // The last rule(s) are the NODATA rules { SingleRuleList<? extends Symbolizer> copyOfNoDataSymbol = getNoDataSymbol() .copy(); List<Rule> rules2 = copyOfNoDataSymbol.getRules(); for (Rule noDataRule : rules2) { if (copyOfNoDataSymbol.isVisibleInLegend()) noDataRule .setName(FeatureRuleList.NODATA_RULE_NAME_SHOWINLEGEND); else noDataRule .setName(FeatureRuleList.NODATA_RULE_NAME_HIDEINLEGEND); noDataRule.setFilter(getNoDataFilter()); rules.add(noDataRule); } } return rules; } /** * Overriding it to add a listener that will automatically propagate the * rulelistechange to the graduate color rule list. */ @Override public SingleRuleList<? extends Symbolizer> getTemplate() { SingleRuleList<? extends Symbolizer> temp = super.getTemplate(); // We may add this listener as often as we want, because the listeners // are registered in a WeakHashSet temp.addListener(listenToTemplateRLChangesAndPropageToGraduateColorsRL); return temp; } @Override public void importRules(List<Rule> rules) { /*********************************************************** * Parsing information in the RULEs * * title, class limits */ int countRules = 0; final TreeSet<Double> classLimits = new TreeSet<Double>(); double[] ds = null; for (final Rule r : rules) { if (r.getName().toString() .startsWith(FeatureRuleList.NODATA_RULE_NAME)) { // This rule defines the NoDataSymbol this.importNoDataRule(r); continue; } // set Title this.getRuleTitles().put(countRules, GTUtil.descriptionTitle(r.getDescription())); // Class Limits Filter filter = r.getFilter(); // Reving and preceeding Enabled/Disabled filter filter = parseAbstractRlSettings(filter); ds = interpretBetweenFilter(filter); classLimits.add(ds[0]); countRules++; } if (ds != null) { // The last limit is only added if there have been // any rules classLimits.add(ds[1]); } setClassLimits(classLimits, false); /** * Now determine the colors stored inside the symbolizers. */ for (int ri = 0; ri < countRules; ri++) { // Import the dominant color from the symbolizers // (they can differ from the palette colors, because // they might have been changed manually. for (final Symbolizer s : rules.get(ri).getSymbolizers()) { final Color c = StylingUtil.getSymbolizerColor(s); if (c != null) { // LOGGER.debug("Rule " + ri + " has color " + c); this.getColors()[ri] = c; break; } } } } /** * Together with {@link #extendMetaInfoString()} this allows loading and * saving the RL */ @Override public void parseMetaInfoString(String metaInfoString, FeatureTypeStyle importFTS) { metaInfoString = metaInfoString .substring(getType().toString().length()); super.parseMetaInfoString(metaInfoString); /*********************************************************************** * Parsing a list of Key-Value Pairs from the FeatureTypeStyleName */ String[] params = metaInfoString.split(METAINFO_SEPERATOR_CHAR); for (String p : params) { String[] kvp = p.split(METAINFO_KVP_EQUALS_CHAR); if (kvp[0].equalsIgnoreCase(KVP_METHOD.toString())) { // We had a typo error in AtlasStyler 1.1 - to correctly import // old styled, we have to correct it here: if (kvp[1].equals("QANTILES")) kvp[1] = "QUANTILES"; setMethod(CLASSIFICATION_METHOD.valueOf(kvp[1])); } else if (kvp[0].equalsIgnoreCase(KVP_PALTETTE)) { String brewerPaletteName = kvp[1]; BrewerPalette foundIt = null; for (BrewerPalette ppp : ASUtil.getPalettes(new PaletteType( true, false), getNumClasses())) { if (ppp.getName().equals(brewerPaletteName)) { foundIt = ppp; break; } } if (foundIt == null) { LOGGER.warn("Couldn't find the palette with the name '" + brewerPaletteName + "'."); } else { setBrewerPalette(foundIt); } } } /*********************************************************************** * Determining the template by analyzing the first rule */ importTemplate(importFTS); } public void setBrewerPalette(BrewerPalette newPalette) { // Only react to real changes if (newPalette.getDescription() != null && newPalette.getDescription().equals( brewerPalette.getDescription())) { return; } this.brewerPalette = newPalette; setColors(null); fireEvents(new RuleChangedEvent("Set brewer palette", this)); } }