/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.styling.css; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.Parameter; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.FeatureCollection; import org.geotools.feature.NameImpl; import org.geotools.styling.css.Value.Function; import org.opengis.feature.type.Name; import org.opengis.filter.FilterFactory2; import org.opengis.filter.capability.FunctionName; import org.opengis.filter.expression.Expression; /** * A value for a CSS property. Values can be several things, including from literals, expressions, * composition of multiple values. * * @author Andrea Aime - GeoSolutions * */ abstract class Value { static final FilterFactory2 FF = CommonFactoryFinder.getFilterFactory2(); public static final Map<String, String> COLORS_TO_HEX; static { COLORS_TO_HEX = new HashMap<String, String>(); COLORS_TO_HEX.put("aliceblue", "#f0f8ff"); COLORS_TO_HEX.put("antiquewhite", "#faebd7"); COLORS_TO_HEX.put("aqua", "#00ffff"); COLORS_TO_HEX.put("aquamarine", "#7fffd4"); COLORS_TO_HEX.put("azure", "#f0ffff"); COLORS_TO_HEX.put("beige", "#f5f5dc"); COLORS_TO_HEX.put("bisque", "#ffe4c4"); COLORS_TO_HEX.put("black", "#000000"); COLORS_TO_HEX.put("blanchedalmond", "#ffebcd"); COLORS_TO_HEX.put("blue", "#0000ff"); COLORS_TO_HEX.put("blueviolet", "#8a2be2"); COLORS_TO_HEX.put("brown", "#a52a2a"); COLORS_TO_HEX.put("burlywood", "#deb887"); COLORS_TO_HEX.put("cadetblue", "#5f9ea0"); COLORS_TO_HEX.put("chartreuse", "#7fff00"); COLORS_TO_HEX.put("chocolate", "#d2691e"); COLORS_TO_HEX.put("coral", "#ff7f50"); COLORS_TO_HEX.put("cornflowerblue", "#6495ed"); COLORS_TO_HEX.put("cornsilk", "#fff8dc"); COLORS_TO_HEX.put("crimson", "#dc143c"); COLORS_TO_HEX.put("cyan", "#00ffff"); COLORS_TO_HEX.put("darkblue", "#00008b"); COLORS_TO_HEX.put("darkcyan", "#008b8b"); COLORS_TO_HEX.put("darkgoldenrod", "#b8860b"); COLORS_TO_HEX.put("darkgray", "#a9a9a9"); COLORS_TO_HEX.put("darkgreen", "#006400"); COLORS_TO_HEX.put("darkgrey", "#a9a9a9"); COLORS_TO_HEX.put("darkkhaki", "#bdb76b"); COLORS_TO_HEX.put("darkmagenta", "#8b008b"); COLORS_TO_HEX.put("darkolivegreen", "#556b2f"); COLORS_TO_HEX.put("darkorange", "#ff8c00"); COLORS_TO_HEX.put("darkorchid", "#9932cc"); COLORS_TO_HEX.put("darkred", "#8b0000"); COLORS_TO_HEX.put("darksalmon", "#e9967a"); COLORS_TO_HEX.put("darkseagreen", "#8fbc8f"); COLORS_TO_HEX.put("darkslateblue", "#483d8b"); COLORS_TO_HEX.put("darkslategray", "#2f4f4f"); COLORS_TO_HEX.put("darkslategrey", "#2f4f4f"); COLORS_TO_HEX.put("darkturquoise", "#00ced1"); COLORS_TO_HEX.put("darkviolet", "#9400d3"); COLORS_TO_HEX.put("deeppink", "#ff1493"); COLORS_TO_HEX.put("deepskyblue", "#00bfff"); COLORS_TO_HEX.put("dimgray", "#696969"); COLORS_TO_HEX.put("dimgrey", "#696969"); COLORS_TO_HEX.put("dodgerblue", "#1e90ff"); COLORS_TO_HEX.put("firebrick", "#b22222"); COLORS_TO_HEX.put("floralwhite", "#fffaf0"); COLORS_TO_HEX.put("forestgreen", "#228b22"); COLORS_TO_HEX.put("fuchsia", "#ff00ff"); COLORS_TO_HEX.put("gainsboro", "#dcdcdc"); COLORS_TO_HEX.put("ghostwhite", "#f8f8ff"); COLORS_TO_HEX.put("gold", "#ffd700"); COLORS_TO_HEX.put("goldenrod", "#daa520"); COLORS_TO_HEX.put("gray", "#808080"); COLORS_TO_HEX.put("grey", "#808080"); COLORS_TO_HEX.put("green", "#008000"); COLORS_TO_HEX.put("greenyellow", "#adff2f"); COLORS_TO_HEX.put("honeydew", "#f0fff0"); COLORS_TO_HEX.put("hotpink", "#ff69b4"); COLORS_TO_HEX.put("indianred", "#cd5c5c"); COLORS_TO_HEX.put("indigo", "#4b0082"); COLORS_TO_HEX.put("ivory", "#fffff0"); COLORS_TO_HEX.put("khaki", "#f0e68c"); COLORS_TO_HEX.put("lavender", "#e6e6fa"); COLORS_TO_HEX.put("lavenderblush", "#fff0f5"); COLORS_TO_HEX.put("lawngreen", "#7cfc00"); COLORS_TO_HEX.put("lemonchiffon", "#fffacd"); COLORS_TO_HEX.put("lightblue", "#add8e6"); COLORS_TO_HEX.put("lightcoral", "#f08080"); COLORS_TO_HEX.put("lightcyan", "#e0ffff"); COLORS_TO_HEX.put("lightgoldenrodyellow", "#fafad2"); COLORS_TO_HEX.put("lightgray", "#d3d3d3"); COLORS_TO_HEX.put("lightgreen", "#90ee90"); COLORS_TO_HEX.put("lightgrey", "#d3d3d3"); COLORS_TO_HEX.put("lightpink", "#ffb6c1"); COLORS_TO_HEX.put("lightsalmon", "#ffa07a"); COLORS_TO_HEX.put("lightseagreen", "#20b2aa"); COLORS_TO_HEX.put("lightskyblue", "#87cefa"); COLORS_TO_HEX.put("lightslategray", "#778899"); COLORS_TO_HEX.put("lightslategrey", "#778899"); COLORS_TO_HEX.put("lightsteelblue", "#b0c4de"); COLORS_TO_HEX.put("lightyellow", "#ffffe0"); COLORS_TO_HEX.put("lime", "#00ff00"); COLORS_TO_HEX.put("limegreen", "#32cd32"); COLORS_TO_HEX.put("linen", "#faf0e6"); COLORS_TO_HEX.put("magenta", "#ff00ff"); COLORS_TO_HEX.put("maroon", "#800000"); COLORS_TO_HEX.put("mediumaquamarine", "#66cdaa"); COLORS_TO_HEX.put("mediumblue", "#0000cd"); COLORS_TO_HEX.put("mediumorchid", "#ba55d3"); COLORS_TO_HEX.put("mediumpurple", "#9370db"); COLORS_TO_HEX.put("mediumseagreen", "#3cb371"); COLORS_TO_HEX.put("mediumslateblue", "#7b68ee"); COLORS_TO_HEX.put("mediumspringgreen", "#00fa9a"); COLORS_TO_HEX.put("mediumturquoise", "#48d1cc"); COLORS_TO_HEX.put("mediumvioletred", "#c71585"); COLORS_TO_HEX.put("midnightblue", "#191970"); COLORS_TO_HEX.put("mintcream", "#f5fffa"); COLORS_TO_HEX.put("mistyrose", "#ffe4e1"); COLORS_TO_HEX.put("moccasin", "#ffe4b5"); COLORS_TO_HEX.put("navajowhite", "#ffdead"); COLORS_TO_HEX.put("navy", "#000080"); COLORS_TO_HEX.put("oldlace", "#fdf5e6"); COLORS_TO_HEX.put("olive", "#808000"); COLORS_TO_HEX.put("olivedrab", "#6b8e23"); COLORS_TO_HEX.put("orange", "#ffa500"); COLORS_TO_HEX.put("orangered", "#ff4500"); COLORS_TO_HEX.put("orchid", "#da70d6"); COLORS_TO_HEX.put("palegoldenrod", "#eee8aa"); COLORS_TO_HEX.put("palegreen", "#98fb98"); COLORS_TO_HEX.put("paleturquoise", "#afeeee"); COLORS_TO_HEX.put("palevioletred", "#db7093"); COLORS_TO_HEX.put("papayawhip", "#ffefd5"); COLORS_TO_HEX.put("peachpuff", "#ffdab9"); COLORS_TO_HEX.put("peru", "#cd853f"); COLORS_TO_HEX.put("pink", "#ffc0cb"); COLORS_TO_HEX.put("plum", "#dda0dd"); COLORS_TO_HEX.put("powderblue", "#b0e0e6"); COLORS_TO_HEX.put("purple", "#800080"); COLORS_TO_HEX.put("red", "#ff0000"); COLORS_TO_HEX.put("rosybrown", "#bc8f8f"); COLORS_TO_HEX.put("royalblue", "#4169e1"); COLORS_TO_HEX.put("saddlebrown", "#8b4513"); COLORS_TO_HEX.put("salmon", "#fa8072"); COLORS_TO_HEX.put("sandybrown", "#f4a460"); COLORS_TO_HEX.put("seagreen", "#2e8b57"); COLORS_TO_HEX.put("seashell", "#fff5ee"); COLORS_TO_HEX.put("sienna", "#a0522d"); COLORS_TO_HEX.put("silver", "#c0c0c0"); COLORS_TO_HEX.put("skyblue", "#87ceeb"); COLORS_TO_HEX.put("slateblue", "#6a5acd"); COLORS_TO_HEX.put("slategray", "#708090"); COLORS_TO_HEX.put("slategrey", "#708090"); COLORS_TO_HEX.put("snow", "#fffafa"); COLORS_TO_HEX.put("springgreen", "#00ff7f"); COLORS_TO_HEX.put("steelblue", "#4682b4"); COLORS_TO_HEX.put("tan", "#d2b48c"); COLORS_TO_HEX.put("teal", "#008080"); COLORS_TO_HEX.put("thistle", "#d8bfd8"); COLORS_TO_HEX.put("tomato", "#ff6347"); COLORS_TO_HEX.put("turquoise", "#40e0d0"); COLORS_TO_HEX.put("violet", "#ee82ee"); COLORS_TO_HEX.put("wheat", "#f5deb3"); COLORS_TO_HEX.put("white", "#ffffff"); COLORS_TO_HEX.put("whitesmoke", "#f5f5f5"); COLORS_TO_HEX.put("yellow", "#ffff00"); COLORS_TO_HEX.put("yellowgreen", "#9acd32"); } /** * Turns this value into a OGC expression. Only literals and expressions can be converted to a * OGC expression */ public org.opengis.filter.expression.Expression toExpression() { throw new UnsupportedOperationException("Cannot turn this value into a OGC expression: " + this); } /** * Turns this value into a literal. Only true literals support this operation */ public String toLiteral() { throw new UnsupportedOperationException("Cannot turn this value into a literal: " + this); } /** * A literal, that is, a static value, represented as a string * * @author Andrea Aime - GeoSolutions * */ static class Literal extends Value { static final Pattern PERCENTAGE = Pattern.compile("(\\d+\\.?\\d*)\\s*%"); public String body; public Literal(String body) { this.body = body; } @Override public String toString() { return "Literal[" + body + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((body == null) ? 0 : body.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Literal other = (Literal) obj; if (body == null) { if (other.body != null) return false; } else if (!body.equals(other.body)) return false; return true; } public org.opengis.filter.expression.Expression toExpression() { Matcher matcher = PERCENTAGE.matcher(body); if (matcher.matches()) { String group = matcher.group(1); Double percentage = Double.valueOf(group); return FF.literal(percentage / 100d); } return FF.literal(body); } public String toLiteral() { return body; } } /** * A function, with a name and parameters * * @author Andrea Aime - GeoSolutions * */ static class Function extends Value { static final String URL = "url"; static final String SYMBOL = "symbol"; public static boolean isGraphicsFunction(Value v) { if(!(v instanceof Function)) { return false; } else { Function f = (Function) v; return URL.equals(f.name) || SYMBOL.equals(f.name); } } public String name; public List<Value> parameters; /** * Builds a function * @param name * @param parameters */ public Function(String name, List<Value> parameters) { super(); this.parameters = parameters; this.name = name; if ((URL.equals(name) || SYMBOL.equals(name)) && parameters.size() != 1) { throw new IllegalArgumentException("Function " + name + " takes a single argument, not " + parameters.size()); } } /** * Builds a function * * @param name * @param parameters */ public Function(String name, Value... parameters) { this(name, Arrays.asList(parameters)); } @Override public String toString() { return "Function [name=" + name + ", parameters=" + parameters + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((parameters == null) ? 0 : parameters.hashCode()); return result; } @Override public org.opengis.filter.expression.Expression toExpression() { // turn function call if possible org.opengis.filter.expression.Expression[] params = this.parameters.stream() .map(v -> v.toExpression()) .toArray(s -> new org.opengis.filter.expression.Expression[s]); return FF.function(this.name, params); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Function other = (Function) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (parameters == null) { if (other.parameters != null) return false; } else if (!parameters.equals(other.parameters)) return false; return true; } } /** * A function, with a name and named parameters * * @author Andrea Aime - GeoSolutions * */ static class TransformFunction extends Value { static final String URL = "url"; static final String SYMBOL = "symbol"; public String name; public Map<String, Value> parameters; /** * Builds a function * @param name * @param parameters */ public TransformFunction(String name, Map<String, Value> parameters) { super(); this.parameters = parameters; this.name = name; if ((URL.equals(name) || SYMBOL.equals(name)) && parameters.size() != 1) { throw new IllegalArgumentException("Function " + name + " takes a single argument, not " + parameters.size()); } } @Override public String toString() { return "TransformFunction [name=" + name + ", parameters=" + parameters + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((parameters == null) ? 0 : parameters.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TransformFunction other = (TransformFunction) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (parameters == null) { if (other.parameters != null) return false; } else if (!parameters.equals(other.parameters)) return false; return true; } @Override public org.opengis.filter.expression.Expression toExpression() { Map<String, Parameter<?>> paramInfo = loadProcessInfo(processName(name)); if(paramInfo == null) { throw new RuntimeException("Could not locate rendering transformation named " + name); } List<org.opengis.filter.expression.Expression> arguments = new ArrayList<>(); // See if we have to add the implicit parameter layer String inputLayerParameter = getInputLayerParameter(paramInfo); if(inputLayerParameter != null && !parameters.containsKey(inputLayerParameter)) { arguments.add(toParamFunction(inputLayerParameter, null)); } // Transform all the parameters we have for (Map.Entry<String, Value> p : parameters.entrySet()) { String key = p.getKey(); Value v = p.getValue(); org.opengis.filter.expression.Expression ex = toParamFunction(key, v); arguments.add(ex); } org.opengis.filter.expression.Expression[] argsArray = toExpressionArray(arguments); return FF.function(name, argsArray); } private String getInputLayerParameter(Map<String, Parameter<?>> paramInfo) { for (Map.Entry<String, Parameter<?>> entry : paramInfo.entrySet()) { if(entry.getValue() != null) { final Class<?> type = entry.getValue().getType(); if(GridCoverage2D.class.isAssignableFrom(type) || FeatureCollection.class.isAssignableFrom(type) || GridCoverage2DReader.class.isAssignableFrom(type)) { return entry.getKey(); } } } return null; } private org.opengis.filter.expression.Expression toParamFunction(String key, Value v) { List<org.opengis.filter.expression.Expression> paramArgs = new ArrayList<>(); // the param name paramArgs.add(FF.literal(key)); if(v instanceof MultiValue) { MultiValue mv = (MultiValue) v; for (Value cv : mv.values) { final org.opengis.filter.expression.Expression ex = cv.toExpression(); paramArgs.add(ex); } } else if(v != null) { final org.opengis.filter.expression.Expression ex = v.toExpression(); paramArgs.add(ex); } org.opengis.filter.expression.Expression[] paramArgsArray = toExpressionArray( paramArgs); org.opengis.filter.expression.Function function = FF.function("parameter", paramArgsArray); return function; } private org.opengis.filter.expression.Expression[] toExpressionArray( List<org.opengis.filter.expression.Expression> arguments) { org.opengis.filter.expression.Expression[] argsArray = (org.opengis.filter.expression.Expression[]) arguments.toArray(new org.opengis.filter.expression.Expression[arguments.size()]); return argsArray; } public static Name processName(String name) { String[] split = name.split(":"); if (split.length == 1) { return new NameImpl(split[0]); } return new NameImpl(split[0], split[1]); } @SuppressWarnings("unchecked") public static Map<String,Parameter<?>> loadProcessInfo(Name name) { Class<?> processorsClass = null; try { processorsClass = Class.forName("org.geotools.process.Processors", false, Value.class.getClassLoader()); Method getParameterInfo = processorsClass.getMethod("getParameterInfo", Name.class); return (Map<String,Parameter<?>>) getParameterInfo.invoke(null, name); } catch(Exception e) { throw new RuntimeException("Error looking up process info", e); } } private boolean isRenderingTransformation(FunctionName fn) { // TODO Auto-generated method stub return false; } } /** * An expression, backed by an OGC {@link org.opengis.filter.expression.Expression} * * @author Andrea Aime - GeoSolutions * */ static class Expression extends Value { public org.opengis.filter.expression.Expression expression; public Expression(org.opengis.filter.expression.Expression expression) { super(); this.expression = expression; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((expression == null) ? 0 : expression.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Expression other = (Expression) obj; if (expression == null) { if (other.expression != null) return false; } else if (!expression.equals(other.expression)) return false; return true; } @Override public String toString() { return "Expression [expression=" + expression + "]"; } public org.opengis.filter.expression.Expression toExpression() { return expression; } public String toLiteral() { throw new UnsupportedOperationException("Cannot turn this value into a literal: " + this); } } /** * A list of values * * @author Andrea Aime - GeoSolutions * */ public static class MultiValue extends Value { List<Value> values; public MultiValue(List<Value> values) { this.values = values; } public MultiValue(Value... values) { this.values = Arrays.asList(values); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((values == null) ? 0 : values.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MultiValue other = (MultiValue) obj; if (values == null) { if (other.values != null) return false; } else if (!values.equals(other.values)) return false; return true; } @Override public String toLiteral() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < values.size(); i++) { sb.append(values.get(i).toLiteral()); if (i < values.size() - 1) { sb.append(" "); } } return sb.toString(); } } }