/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.ui.style; import java.awt.Color; import java.util.HashSet; import java.util.Iterator; import java.util.Map.Entry; import java.util.Random; import java.util.Set; import javax.annotation.Nullable; import org.eclipse.ui.PlatformUI; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.NameImpl; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Fill; import org.geotools.styling.LineSymbolizer; import org.geotools.styling.Mark; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.SLD; import org.geotools.styling.Style; import org.geotools.styling.StyleBuilder; import org.geotools.styling.StyleFactory; import org.geotools.styling.Symbolizer; import org.opengis.filter.FilterFactory; import com.google.common.collect.SetMultimap; import eu.esdihumboldt.hale.common.instance.model.DataSet; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.constraint.type.AbstractFlag; import eu.esdihumboldt.hale.ui.geometry.service.GeometrySchemaService; import eu.esdihumboldt.hale.ui.style.service.internal.StylePreferences; /** * Style helper methods * * @author Simon Templer * @partner 01 / Fraunhofer Institute for Computer Graphics Research */ public abstract class StyleHelper { /** * Default fill opacity */ public static final double DEFAULT_FILL_OPACITY = 0.4; private static final StyleBuilder styleBuilder = new StyleBuilder(); private static final StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null); private static final FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null); /** * Returns a default style for the given type. * * @param typeDef the type definition * @param dataSet the data set (if known) * @return the style */ public static FeatureTypeStyle getDefaultStyle(TypeDefinition typeDef, @Nullable DataSet dataSet) { // GeometrySchemaService gss = (GeometrySchemaService) PlatformUI.getWorkbench().getService(GeometrySchemaService.class); // List<QName> geomPath = gss.getDefaultGeometry(typeDef); // TODO determine default style from default geometry? Color defColor; if (dataSet != null) { defColor = StylePreferences.getDefaultColor(dataSet); } else { defColor = Color.DARK_GRAY; } int defWidth = StylePreferences.getDefaultWidth(); FeatureTypeStyle result; // XXX for now create a polygon style in any case, as it contains fill // and stroke // if (type != null) { // if (type.isAssignableFrom(Polygon.class) // || type.isAssignableFrom(MultiPolygon.class)) { result = createPolygonStyle(defColor, defWidth); // } else if (type.isAssignableFrom(LineString.class) // || type.isAssignableFrom(MultiLineString.class)) { // result = createLineStyle(defColor, defWidth); // } else { // result = createPointStyle(defColor, defWidth); // } // } // else { // result = createPointStyle(defColor, defWidth); // } // XXX StyleBuilder does not support feature type names with namespace // QName name = getFeatureTypeName(typeDef); // result.featureTypeNames().add(new NameImpl(name.getNamespaceURI(), name.getLocalPart())); result.featureTypeNames().add(new NameImpl(getFeatureTypeName(typeDef))); return result; } /** * Returns a default style for the given type. * * @param dataSetTypes type definitions associated to their data set * @return the style */ public static Style getRandomStyles(SetMultimap<DataSet, TypeDefinition> dataSetTypes) { int defWidth = StylePreferences.getDefaultWidth(); Style style = styleFactory.createStyle(); for (Entry<DataSet, TypeDefinition> entry : dataSetTypes.entries()) { DataSet dataSet = entry.getKey(); TypeDefinition typeDef = entry.getValue(); FeatureTypeStyle fts; // TODO based on default geometry? // polygon is always OK as it contains stroke and fill // Color color = generateRandomColor(Color.WHITE); float saturation; float brightness; switch (dataSet) { case TRANSFORMED: saturation = 0.8f; brightness = 0.6f; break; case SOURCE: default: saturation = 0.75f; brightness = 0.8f; break; } Color color = generateRandomColor(saturation, brightness); fts = createPolygonStyle(color, defWidth); fts.featureTypeNames().add(new NameImpl(getFeatureTypeName(typeDef))); style.featureTypeStyles().add(fts); } return style; } /** * Returns a default style for the given type. * * @param dataSetTypes type definitions associated to their data set * @return the style */ public static Style getSpectrumStyles(SetMultimap<DataSet, TypeDefinition> dataSetTypes) { int defWidth = StylePreferences.getDefaultWidth(); Style style = styleFactory.createStyle(); GeometrySchemaService gss = PlatformUI.getWorkbench() .getService(GeometrySchemaService.class); for (DataSet dataSet : dataSetTypes.keySet()) { float saturation; float brightness; switch (dataSet) { case TRANSFORMED: saturation = 0.8f; brightness = 0.6f; break; case SOURCE: default: saturation = 0.75f; brightness = 0.8f; break; } Set<TypeDefinition> types = new HashSet<>(dataSetTypes.get(dataSet)); Iterator<TypeDefinition> it = types.iterator(); while (it.hasNext()) { TypeDefinition type = it.next(); // remove invalid types if (type.getConstraint(AbstractFlag.class).isEnabled() || gss.getDefaultGeometry(type) == null) { it.remove(); } } int numberOfTypes = types.size(); int index = 0; for (TypeDefinition typeDef : types) { FeatureTypeStyle fts; // TODO based on default geometry? // polygon is always OK as it contains stroke and fill // Color color = generateRandomColor(Color.WHITE); Color color; if (numberOfTypes == 1) { color = generateRandomColor(saturation, brightness); } else { color = Color.getHSBColor((float) index / (float) numberOfTypes, saturation, brightness); } fts = createPolygonStyle(color, defWidth); fts.featureTypeNames().add(new NameImpl(getFeatureTypeName(typeDef))); style.featureTypeStyles().add(fts); index++; } } return style; } /** * Generate a random color. Mixing in WHITE will create pastel colors. * Mixing in a pastel color will create tinted colors. * * @param mix color to mix in (use average of RGB values) * @return the generated color */ public static Color generateRandomColor(@Nullable Color mix) { Random random = new Random(); int red = random.nextInt(256); int green = random.nextInt(256); int blue = random.nextInt(256); // mix the color if (mix != null) { red = (red + mix.getRed()) / 2; green = (green + mix.getGreen()) / 2; blue = (blue + mix.getBlue()) / 2; } Color color = new Color(red, green, blue); return color; } private static float GOLDEN_RATIO_CONJUGATE = 0.618033988749895f; /** * Generate a random color. * * Inspired by * http://martin.ankerl.com/2009/12/09/how-to-create-random-colors * -programmatically/ * * @param saturation the saturation (between 0.0f and 1.0f) * @param brightness the brightness (between 0.0f and 1.0f) * * @return the random color */ public static Color generateRandomColor(float saturation, float brightness) { Random random = new Random(); float rand = random.nextFloat(); rand = rand + GOLDEN_RATIO_CONJUGATE; rand = rand % 1; return Color.getHSBColor(rand, saturation, brightness); } // XXX StyleBuilder does not support feature type names with namespace // /** // * Get the name used in styles for the given type definition. // * @param typeDef the type definition // * @return the feature type name // */ // public static QName getFeatureTypeName(TypeDefinition typeDef) { // // default to element name // Collection<? extends XmlElement> elements = typeDef.getConstraint(XmlElements.class).getElements(); // if (elements.size() == 1) { // // only use element name if it is unique // return elements.iterator().next().getName(); // } // // // type // return typeDef.getName(); // } /** * Get the name used in styles for the given type definition. * * @param typeDef the type definition * @return the feature type name */ public static String getFeatureTypeName(TypeDefinition typeDef) { // type or element name return typeDef.getDisplayName(); } /** * Get a style containing the default style for the given type. * * @param type the type definition * @param dataSet the data set * @return the style with the default type style */ public static Style getStyle(TypeDefinition type, DataSet dataSet) { Style style = styleFactory.createStyle(); style.featureTypeStyles().add(getDefaultStyle(type, dataSet)); return style; } /** * Create a new point symbolizer based on the given one. * * @param symbolizer the point symbolizer * @param color the new color * @param width the new line width * @return the mutated symbolizer */ public static Symbolizer mutatePointSymbolizer(PointSymbolizer symbolizer, Color color, int width) { // mutate mark Mark mark = SLD.mark(symbolizer); Mark mutiMark = styleBuilder.createMark(mark.getWellKnownName(), styleBuilder.createFill(color, DEFAULT_FILL_OPACITY), styleBuilder.createStroke(color, width)); // XXX commented because unsupported in Geotools 8.0-M1 // mutiMark.setSize(mark.getSize()); // mutiMark.setRotation(mark.getRotation()); // create new symbolizer return styleBuilder.createPointSymbolizer(styleBuilder.createGraphic(null, mutiMark, null)); } /** * Manually create a default point style. * * @param color the point color * @param width the line width * @return a Style for Point objects. */ @SuppressWarnings("unused") private static FeatureTypeStyle createPointStyle(Color color, double width) { PointSymbolizer symbolizer = createPointSymbolizer(color, width); // symbolizer.getGraphic().setSize(filterFactory.literal(1)); Rule rule = styleFactory.createRule(); rule.symbolizers().add(symbolizer); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(); fts.rules().add(rule); return fts; } /** * Create a default point symbolizer. * * @param color the color * @param width the line width * @return the point symbolizer */ public static PointSymbolizer createPointSymbolizer(Color color, double width) { return styleBuilder.createPointSymbolizer(styleBuilder.createGraphic(null, styleBuilder.createMark(StyleBuilder.MARK_X, styleBuilder.createFill(color, DEFAULT_FILL_OPACITY), styleBuilder.createStroke(color, width)), null)); } /** * Create a default line style. * * @param color the line color * @param width the line width * @return a Style for Line/LineString objects. */ @SuppressWarnings("unused") private static FeatureTypeStyle createLineStyle(Color color, double width) { LineSymbolizer symbolizer = createLineSymbolizer(color, width); Rule rule = styleFactory.createRule(); rule.symbolizers().add(symbolizer); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(); fts.rules().add(rule); return fts; } /** * Create a default line symbolizer. * * @param color the color * @param width the line width * @return the line symbolizer */ public static LineSymbolizer createLineSymbolizer(Color color, double width) { LineSymbolizer symbolizer = styleFactory.createLineSymbolizer(); SLD.setLineColour(symbolizer, color); symbolizer.getStroke().setWidth(filterFactory.literal(width)); return symbolizer; } /** * Create a default polygon style. * * @param color the polygon color * @param width the line width * @return a Style for Polygon objects */ private static FeatureTypeStyle createPolygonStyle(Color color, double width) { PolygonSymbolizer symbolizer = createPolygonSymbolizer(color, width); Rule rule = styleFactory.createRule(); rule.symbolizers().add(symbolizer); FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(); fts.rules().add(rule); return fts; } /** * Create a default polygon symbolizer. * * @param color the color * @param width the line width * @return the polygon symbolizer */ public static PolygonSymbolizer createPolygonSymbolizer(Color color, double width) { PolygonSymbolizer symbolizer = styleFactory.createPolygonSymbolizer(); SLD.setPolyColour(symbolizer, color); symbolizer.getStroke().setWidth(filterFactory.literal(width)); Fill fill = styleFactory.createFill(filterFactory.literal(color), filterFactory.literal(DEFAULT_FILL_OPACITY)); symbolizer.setFill(fill); return symbolizer; } }