/******************************************************************************* * 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; import java.awt.Dimension; import java.awt.Font; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.swing.JOptionPane; import org.apache.log4j.Logger; import org.geopublishing.atlasStyler.rulesLists.AbstractRulesList; import org.geopublishing.atlasStyler.rulesLists.FeatureRuleList; import org.geopublishing.atlasStyler.rulesLists.RulesListInterface; import org.geopublishing.atlasStyler.rulesLists.SingleRuleList; import org.geotools.data.FeatureSource; import org.geotools.map.MapLayer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Style; import org.geotools.styling.Symbolizer; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.expression.Literal; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Polygon; import de.schmitzm.geotools.FilterUtil; import de.schmitzm.geotools.data.amd.AttributeMetadataImplMap; import de.schmitzm.geotools.data.amd.AttributeMetadataInterface; import de.schmitzm.geotools.data.amd.AttributeMetadataMap; import de.schmitzm.geotools.feature.FeatureUtil; import de.schmitzm.geotools.feature.FeatureUtil.GeometryForm; import de.schmitzm.geotools.styling.StyledFS; import de.schmitzm.geotools.styling.StyledFeaturesInterface; import de.schmitzm.geotools.styling.StyledLayerInterface; import de.schmitzm.geotools.styling.StylingUtil; import de.schmitzm.lang.LangUtil; /** * The {@link AtlasStylerVector} is a class to create SLD documents for a * {@link FeatureSource}. It contains no GUI. * * @author Stefan A. Tzeggai * */ public class AtlasStylerVector extends AtlasStyler { /** All {@link AtlasStylerVector} related files will be saved blow this path */ private static File applicationPreferencesDir; /** * Default size of a symbol in any AtlasStyler previews */ public static final Dimension DEFAULT_SYMBOL_PREVIEW_SIZE = new Dimension( 30, 30); /** These DIRNAMEs describe paths to application files on the local machines */ final static String DIRNAME_LINE = "line"; /** These DIRNAMEs describe paths to application files on the local machines */ final static String DIRNAME_POINT = "point"; /*************************************************************************** * Paths */ /** These DIRNAMEs describe paths to application files on the local machines */ final static String DIRNAME_POLYGON = "polygon"; // // TODO ?? // /** These DIRNAMEs describe paths to application files on the local // machines */ // final static String DIRNAME_ANY = "any"; private final static Logger LOGGER = LangUtil .createLogger(AtlasStylerVector.class); /** * @return A {@link File} pointing to USER_HOME_DIR/.AtlasSLDEditor */ public static File getApplicationPreferencesDir() { if (applicationPreferencesDir == null) { applicationPreferencesDir = new File(new File( System.getProperty("user.home")), ".AtlasStyler"); applicationPreferencesDir.mkdirs(); } return applicationPreferencesDir; } /** * @return a {@link File} that points to the base folder for AtlasStyler * templates */ private static File getBaseSymbolsDir() { return new File(AtlasStylerVector.getApplicationPreferencesDir(), DIRNAME_TEMPLATES); } /** * Returns the list of default font families that are expected to work on * any system. The returned {@link List} contains alternative names for all * the font-families. */ public static List<Literal>[] getDefaultFontFamilies() { ArrayList<Literal>[] fontFamilies = new ArrayList[5]; /** * Every group represents the aliases of similar fonts on different * systems. @see http://www.ampsoft.net/webdesign-l/WindowsMacFonts.html */ fontFamilies[0] = new ArrayList<Literal>(); fontFamilies[0].add(FilterUtil.FILTER_FAC.literal("Arial")); fontFamilies[0].add(FilterUtil.FILTER_FAC.literal("Helvetica")); fontFamilies[0].add(FilterUtil.FILTER_FAC.literal("sans-serif")); fontFamilies[1] = new ArrayList<Literal>(); fontFamilies[1].add(FilterUtil.FILTER_FAC.literal("Arial Black")); fontFamilies[1].add(FilterUtil.FILTER_FAC.literal("Gadget")); fontFamilies[1].add(FilterUtil.FILTER_FAC.literal("sans-serif")); fontFamilies[2] = new ArrayList<Literal>(); fontFamilies[2].add(FilterUtil.FILTER_FAC.literal("Courier New")); fontFamilies[2].add(FilterUtil.FILTER_FAC.literal("Courier")); fontFamilies[2].add(FilterUtil.FILTER_FAC.literal("monospace")); fontFamilies[3] = new ArrayList<Literal>(); fontFamilies[3].add(FilterUtil.FILTER_FAC.literal("Times New Roman")); fontFamilies[3].add(FilterUtil.FILTER_FAC.literal("Times")); fontFamilies[3].add(FilterUtil.FILTER_FAC.literal("serif")); fontFamilies[4] = new ArrayList<Literal>(); fontFamilies[4].add(FilterUtil.FILTER_FAC.literal("Impact")); fontFamilies[4].add(FilterUtil.FILTER_FAC.literal("Charcoal")); fontFamilies[4].add(FilterUtil.FILTER_FAC.literal("sans-serif")); return fontFamilies; } /** * @return A {@link File} object pointing to the directory where the local * LINE symbol library is saved. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public static File getLineSymbolsDir() { final File dir = new File(getBaseSymbolsDir(), DIRNAME_LINE); dir.mkdirs(); return dir; } /** * @return A {@link File} object pointing to the directory where the local * POINT symbol library is saved. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public static File getPointSymbolsDir() { final File dir = new File(getBaseSymbolsDir(), DIRNAME_POINT); dir.mkdirs(); return dir; } /** * @return A {@link File} object pointing to the directory where the local * POLYGON symbol library is saved. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public static File getPolygonSymbolsDir() { final File dir = new File(getBaseSymbolsDir(), DIRNAME_POLYGON); dir.mkdirs(); return dir; } public static File getSymbolsDir(final GeometryForm defaultGeometry) { switch (defaultGeometry) { case LINE: return getLineSymbolsDir(); case POINT: return getPointSymbolsDir(); case POLYGON: return getPolygonSymbolsDir(); case ANY: } final String msg = "GeometryForm not recognized = " + defaultGeometry; LOGGER.error(msg); throw new IllegalArgumentException(msg); } /** * Holds optional meta-information about the data-source. If not set, this * empty Map is used. Otherwise meta like column name description will be * looked up here. **/ private AttributeMetadataMap<? extends AttributeMetadataInterface> attributeMetaDataMap = new AttributeMetadataImplMap(); private final StyledFeaturesInterface<?> styledFeatures; /** * Create an AtlasStyler object for any {@link FeatureSource} */ public AtlasStylerVector( FeatureSource<SimpleFeatureType, SimpleFeature> featureSource) { this(new StyledFS(featureSource)); } /** * Create an AtlasStyler object for any {@link FeatureSource} and import the * given {@link Style}. */ public AtlasStylerVector( FeatureSource<SimpleFeatureType, SimpleFeature> featureSource, Style style) { this(new StyledFS(featureSource), style, null, null, null); } /** * Create an {@link AtlasStylerVector} object for any * {@link StyledFeaturesInterface} */ public AtlasStylerVector(StyledFeaturesInterface<?> styledFeatures) { this(styledFeatures, null, null, null, null); } /*************************************************************************** * Constructor that starts styling a {@link StyledFeaturesInterface}. Loads * the given paramter <code>loadStyle</code> {@link Style} at construction * time. The parameters mapLegend and maplegend are needed to open the * Filter dialog. * * @param StyledFeaturesInterface * Where the features come from. * @param mapLegend * may be <code>null</code> * @param mapLayer * may be <code>null</code> * @param withDefaults * If <code>true</code>, and no RuleList can be imported, a * default Rulelist will be added. */ public AtlasStylerVector(final StyledFeaturesInterface<?> styledFeatures, Style loadStyle, final MapLayer mapLayer, HashMap<String, Object> params, Boolean withDefaults) { super(mapLayer, params, withDefaults); setTitle(styledFeatures.getTitle()); this.styledFeatures = styledFeatures; this.rlf = new RuleListFactory(styledFeatures); if (loadStyle != null) { // Correct propertynames against the Schema loadStyle = StylingUtil.correctPropertyNames(loadStyle, styledFeatures.getSchema()); importStyle(loadStyle); } else { if (styledFeatures.getStyle() != null) { importStyle(styledFeatures.getStyle()); } else { if (withDefaults != null && withDefaults == true) { final SingleRuleList<? extends Symbolizer> defaultRl = rlf .createSingleRulesList( getRuleTitleFor(styledFeatures), true); LOGGER.debug("Added default rulelist: " + defaultRl); addRulesList(defaultRl); } } } setAttributeMetaDataMap(styledFeatures.getAttributeMetaDataMap()); } /** * When switching the ruleliste GUI, the user may be asked to use a single * symbol as a template in another {@link FeatureRuleList}. * * @param oldRl * previously selected RuleList * @param newRl * newly selected RuleList */ void askToTransferTemplates(RulesListInterface oldRl, FeatureRuleList newRl) { if (oldRl == null || newRl == null || oldRl == newRl) return; if (oldRl.getGeometryForm() != newRl.getGeometryForm()) return; /** * Trying it this way: If we switched from a SinglePointSymbolRuleList, * let it be this GraduatedPointColorRuleList's template. */ if (oldRl instanceof SingleRuleList) { SingleRuleList<Symbolizer> oldSingleRl = (SingleRuleList<Symbolizer>) oldRl; final SingleRuleList<Symbolizer> singleRL = oldSingleRl; if (!StylingUtil.isStyleDifferent(singleRL.getFTS(), newRl .getTemplate().getFTS())) return; int res = JOptionPane.NO_OPTION; if (owner != null) { res = JOptionPane .showConfirmDialog( owner, R("AtlasStyler.SwitchRuleListType.CopySingleSymbolAsTemplate")); } if (res == JOptionPane.YES_OPTION) { newRl.setTemplate(singleRL.copy()); } } /** * Trying it this way: If we switched from a SinglePointSymbolRuleList, * let it be this GraduatedPointColorRuleList's template. */ if (oldRl instanceof FeatureRuleList) { FeatureRuleList oldFeatureRl = (FeatureRuleList) oldRl; final SingleRuleList<? extends Symbolizer> oldTemplate = oldFeatureRl .getTemplate(); if (!StylingUtil.isStyleDifferent(oldTemplate.getFTS(), newRl .getTemplate().getFTS())) return; int res = JOptionPane.NO_OPTION; if (owner != null) { res = JOptionPane.showConfirmDialog(null, R("AtlasStyler.SwitchRuleListType.CopyTemplate")); } if (res == JOptionPane.YES_OPTION) { newRl.setTemplate(oldTemplate.copy()); } } } /** * When switching the ruleliste GUI, the user may be asked to use a selected * template as a new {@link SingleRuleList} * * @param oldRl * previously selected RuleList * @param newRl * newly selected RuleList */ void askToTransferTemplates(RulesListInterface oldRl, SingleRuleList newRl) { if (oldRl == null || oldRl == newRl) return; if (oldRl.getGeometryForm() != newRl.getGeometryForm()) return; /** * Trying it this way: If we switched from a * GraduatedColorPointRuleList, let this SinglePointSymbolRuleList be * the GraduatedColorPointRuleList's template. */ if (oldRl instanceof FeatureRuleList) { FeatureRuleList oldFeatureRl = (FeatureRuleList) getLastChangedRuleList(); final SingleRuleList oldTemplate = oldFeatureRl.getTemplate(); if (!StylingUtil.isStyleDifferent(oldTemplate.getFTS(), newRl.getFTS())) return; int res = JOptionPane.NO_OPTION; if (owner != null) { JOptionPane.showConfirmDialog(null, R("AtlasStyler.SwitchRuleListType.CopyTemplate")); } if (res == JOptionPane.YES_OPTION) { newRl.setSymbolizers(oldTemplate.getSymbolizers()); } } /** * Trying it this way: If we switched from a SinglePointSymbolRuleList, * let it be this GraduatedPointColorRuleList's template. */ if (oldRl instanceof SingleRuleList) { // TODO Untested, since it can't happen yet SingleRuleList oldSingleRl = (SingleRuleList) oldRl; if (!StylingUtil.isStyleDifferent(oldSingleRl.getFTS(), newRl.getFTS())) return; int res = JOptionPane.NO_OPTION; if (owner != null) { res = JOptionPane .showConfirmDialog( null, R("AtlasStyler.SwitchRuleListType.CopySingleSymbolAsSingleSymbol")); } if (res == JOptionPane.YES_OPTION) newRl.setSymbolizers(oldSingleRl.getSymbolizers()); } } /** * Creates a copy of any given RulesList */ @Override public AbstractRulesList copyRulesList(RulesListInterface rl) { FeatureTypeStyle fts = rl.getFTS(); try { return getRlf().importFts(fts, false); } catch (AtlasStylerParsingException e) { LOGGER.warn("Trying to copy RL=" + rl + " failed.", e); return null; } } public AttributeMetadataMap<? extends AttributeMetadataInterface> getAttributeMetaDataMap() { return attributeMetaDataMap; } /** * Returns a list of available fonts: A combination of the the * AtlasStyler.getDefaultFontFamilies() and the fonts passed to * {@link AtlasStylerVector} on construction. */ public List<Literal>[] getAvailableFonts() { List<Literal>[] fontFamilies = AtlasStylerVector .getDefaultFontFamilies(); /** * Add user defined Fonts. One Family for every extra font. */ List<Font> extraFonts = getFonts(); int i = fontFamilies.length; for (Font f : extraFonts) { fontFamilies = LangUtil.extendArray(fontFamilies, new ArrayList<Literal>()); fontFamilies[i].add(FilterUtil.FILTER_FAC.literal(f.getName())); i++; } return fontFamilies; } public StyledFeaturesInterface<?> getStyledFeatures() { return styledFeatures; } @Override public StyledLayerInterface<?> getStyledInterface() { return styledFeatures; } /** * Convenience method to indicate if the {@link FeatureSource} is of type * {@link LineString}. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public boolean isLineString() { return FeatureUtil.getGeometryForm(getStyledFeatures() .getFeatureSource()) == GeometryForm.LINE; } /** * Convenience method to indicate if the {@link FeatureSource} is of type * {@link Polygon}. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public boolean isPoint() { return FeatureUtil.getGeometryForm(getStyledFeatures() .getFeatureSource()) == GeometryForm.POINT; } /** * Convenience method to indicate if the {@link FeatureSource} is of type * Polygon. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public boolean isPolygon() { // TODO rethink?! ANY != POLYGON if (FeatureUtil.getGeometryForm(getStyledFeatures().getFeatureSource()) == GeometryForm.ANY) return true; return FeatureUtil.getGeometryForm(getStyledFeatures() .getFeatureSource()) == GeometryForm.POLYGON; } @Override public Style sanitize(Style style) { return StylingUtil.correctPropertyNames(styleCached, getStyledFeatures().getSchema()); } public void setAttributeMetaDataMap( final AttributeMetadataMap<? extends AttributeMetadataInterface> attributeMetaDataMap) { this.attributeMetaDataMap = attributeMetaDataMap; } }