/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.catalog; import java.awt.Color; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.geoserver.catalog.StyleGenerator.ColorRamp.Entry; import org.geotools.styling.StyledLayerDescriptor; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Generates pseudo random styles using a specified color ramp * * @author Andrea Aime - OpenGeo * */ public class StyleGenerator { private ColorRamp ramp; private Catalog catalog; /** * Workspace to create styles relative to */ private WorkspaceInfo workspace; /** * Builds a style generator with the default color ramp * @param catalog */ public StyleGenerator(Catalog catalog) { this.catalog = catalog; ramp = new ColorRamp(); ramp.add("red", Color.decode("0xFF3300")); ramp.add("orange", Color.decode("0xFF6600")); ramp.add("dark orange", Color.decode("0xFF9900")); ramp.add("gold", Color.decode("0xFFCC00")); ramp.add("yellow", Color.decode("0xFFFF00")); ramp.add("dark yellow", Color.decode("0x99CC00")); ramp.add("teal", Color.decode("0x00CC33")); ramp.add("cyan", Color.decode("0x0099CC")); ramp.add("azure", Color.decode("0x0033CC")); ramp.add("violet", Color.decode("0x3300FF")); randomizeRamp(); } protected void randomizeRamp() { ramp.initRandom(); } public StyleGenerator(Catalog catalog, ColorRamp ramp) { if (ramp == null) throw new NullPointerException("The color ramp cannot be null"); this.ramp = ramp; this.catalog = catalog; } /** * Set the workspace to generate styles in. * @param workspace */ public void setWorkspace(WorkspaceInfo workspace) { this.workspace = workspace; } /** * Generate a style for a resource in the catalog, and add the created style to the catalog. * * @param handler The StyleHandler used to generate the style. Determines the style format. * @param featureType The FeatureType to generate the style for. Determines the style type and * style name * @return The StyleInfo referencing the generated style * @throws IOException */ public StyleInfo createStyle(StyleHandler handler, FeatureTypeInfo featureType) throws IOException { return createStyle(handler, featureType, featureType.getFeatureType()); } /** * Generate a style for a resource in the catalog, and add the created style to the catalog. * * @param handler The StyleHandler used to generate the style. Determines the style format. * @param featureType The FeatureType to generate the style for. Determines the style type and * style name * @param nativeFeatureType The geotools feature type, required in cases where featureType is * missing content * @return The StyleInfo referencing the generated style * @throws IOException */ public StyleInfo createStyle(StyleHandler handler, FeatureTypeInfo featureType, FeatureType nativeFeatureType) throws IOException { // geometryless, style it randomly GeometryDescriptor gd = nativeFeatureType.getGeometryDescriptor(); if (gd == null) return catalog.getStyleByName(StyleInfo.DEFAULT_POINT); Class gtype = gd.getType().getBinding(); StyleType st; if (LineString.class.isAssignableFrom(gtype) || MultiLineString.class.isAssignableFrom(gtype)) { st = StyleType.LINE; } else if (Polygon.class.isAssignableFrom(gtype) || MultiPolygon.class.isAssignableFrom(gtype)) { st = StyleType.POLYGON; } else if (Point.class.isAssignableFrom(gtype) || MultiPoint.class.isAssignableFrom(gtype)) { st = StyleType.POINT; } else { st = StyleType.GENERIC; } return doCreateStyle(handler, st, featureType); } /** * Generate a style for a resource in the catalog, and add the created style to the catalog. * * @param handler The StyleHandler used to generate the style. Determines the style format. * @param coverage The CoverageInfo to generate the style for. Determines the style type and * style name * @return The StyleInfo referencing the generated style * @throws IOException */ public StyleInfo createStyle(StyleHandler handler, CoverageInfo coverage) throws IOException { return doCreateStyle(handler, StyleType.RASTER, coverage); } /** * Generate a style of the give style type. * * @param handler The StyleHandler used to generate the style. Determines the style format. * @param styleType The type of template, see {@link org.geoserver.catalog.StyleType}. * @param layerName The name of the style/layer; used in comments. * @return The generated style, as a String. * @throws IOException */ public String generateStyle(StyleHandler handler, StyleType styleType, String layerName) throws IOException { Entry color = ramp.next(); try { return handler.getStyle(styleType, color.color, color.name, layerName); } catch (UnsupportedOperationException e) { //Handler does not support loading from template; load SLD template and convert try { SLDHandler sldHandler = new SLDHandler(); String sldTemplate = sldHandler.getStyle(styleType, color.color, color.name, layerName); StyledLayerDescriptor sld = sldHandler.parse(sldTemplate, null, null, null); ByteArrayOutputStream out = new ByteArrayOutputStream(); handler.encode(sld, null, true, out); return out.toString(); } catch (UnsupportedOperationException e1) { String message = "Error generating style"; boolean directMessage = e.getMessage() != null && !"".equals(e.getMessage().trim()); if (directMessage) { message += " - Direct generation failed with error: " + e.getMessage(); } if (e1.getMessage() != null && !"".equals(e1.getMessage().trim())) { if (directMessage) { message += ", and "; } else { message += " - "; } message += "SLD conversion failed with error: " + e1.getMessage(); } throw new UnsupportedOperationException(message, e1); } } } /** * Generates a unique style name for the specified resource. */ public String generateUniqueStyleName(ResourceInfo resource) { return workspace != null ? findUniqueStyleName(resource, workspace) : findUniqueStyleName(resource); } StyleInfo doCreateStyle(StyleHandler handler, StyleType styleType, ResourceInfo resource) throws IOException { // find a new style name String styleName = generateUniqueStyleName(resource); // variable replacement String styleData = generateStyle(handler, styleType, styleName); // let's store it StyleInfo style = catalog.getFactory().createStyle(); style.setName(styleName); style.setFilename(styleName + "." + handler.getFileExtension()); style.setFormat(handler.getFormat()); if (workspace != null) { style.setWorkspace(workspace); } catalog.getResourcePool().writeStyle(style, new ByteArrayInputStream(styleData.getBytes())); return style; } String findUniqueStyleName(ResourceInfo resource) { String styleName = resource.getStore().getWorkspace().getName() + "_" + resource.getName(); StyleInfo style = catalog.getStyleByName(styleName); int i = 1; while(style != null) { styleName = resource.getStore().getWorkspace().getName() + "_" + resource.getName() + i; style = catalog.getStyleByName(styleName); i++; } return styleName; } String findUniqueStyleName(ResourceInfo resource, WorkspaceInfo workspace) { String styleName = resource.getName(); StyleInfo style = catalog.getStyleByName(workspace, styleName); int i = 1; while(style != null) { styleName = resource.getName() + i; style = catalog.getStyleByName(workspace, styleName); i++; } return styleName; } /** * A rolling color ramp with color names */ public static class ColorRamp { static class Entry { String name; Color color; Entry(String name, Color color) { this.name = name; this.color = color; } } List<Entry> entries = new ArrayList<Entry>(); int position; /** * Builds an empty ramp. Mind, you need to call {@link #add(String, Color)} at least * once to make the ramp usable. */ public ColorRamp() { } /** * Adds a name/color combination * @param name * @param color */ public void add(String name, Color color) { entries.add(new Entry(name, color)); } /** * Moves to the next color in the ramp */ public Entry next() { position = (position+1) % entries.size(); return entries.get(position); } /** * Sets the current ramp position to a random index */ public void initRandom() { position = (int) (entries.size() * Math.random()); } } }