/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2016 Open Source Geospatial Foundation (OSGeo)
* (C) 2014-2016 Boundless Spatial
*
* 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.ysld.parse;
import org.geotools.data.Parameter;
import org.geotools.filter.FunctionFactory;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.ysld.ProcessUtil;
import org.geotools.ysld.YamlMap;
import org.geotools.ysld.YamlObject;
import org.opengis.feature.type.Name;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Function;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.geotools.ysld.ProcessUtil.*;
/**
* Handles parsing a Ysld "transform" property into a transformation {@link Function} object.
*
*/
public class TransformHandler extends YsldParseHandler {
FeatureTypeStyle featureStyle;
int processes = 0;
FunctionFactory functionFactory = loadProcessFunctionFactory();
protected TransformHandler(FeatureTypeStyle featureStyle, Factory factory) {
super(factory);
this.featureStyle = featureStyle;
}
@Override
public void handle(YamlObject<?> obj, YamlParseContext context) {
// lookup process function factory, if null means process modules not on classpath
if (functionFactory == null) {
LOG.warning(
"Unable to load process factory, ignoring transform, ensure process modules installed");
return;
}
YamlMap map = obj.map();
Function function = process(map);
featureStyle.setTransformation(function);
}
Expression envVar(String name) {
return factory.filter.function("env", factory.filter.literal(name));
}
private Function process(YamlMap map) {
processes++; // Found a new process in the chain.
String name = map.str("name");
if (name == null) {
throw new IllegalArgumentException("transform must specify a name");
}
String input = map.str("input");
Name qName = processName(name);
// load process parameter info
Map<String, Parameter<?>> processInfo = loadProcessInfo(qName);
if (processInfo == null) {
throw new IllegalArgumentException("No such process: " + name);
}
boolean wmsParams = ProcessUtil.hasWMSParams(processInfo);
FilterFactory filterFactory = factory.filter;
// turn properties into inputs for ProcessFunction
List<Expression> processArgs = new ArrayList<>();
Expression outputBBOX = null;
Expression outputWidth = null;
Expression outputHeight = null;
if (wmsParams) {
outputBBOX = paramExpression("outputBBOX",
Collections.singletonList(envVar("wms_bbox")));
outputWidth = paramExpression("outputWidth",
Collections.singletonList(envVar("wms_width")));
outputHeight = paramExpression("outputHeight",
Collections.singletonList(envVar("wms_height")));
}
YamlMap params = map.map("params");
if (params != null) {
for (Map.Entry<String, Object> e : params.raw().entrySet()) {
String key = e.getKey();
Object val = e.getValue();
List<Expression> valueArgs = new ArrayList<>();
Parameter<?> p = processInfo.get(key);
if (p != null) {
if (val instanceof String) {
Expression expr = Util.expression((String) val, true, factory);
if (expr != null) {
valueArgs.add(expr);
}
}
if (valueArgs.isEmpty()) {
convertAndAdd(val, p, valueArgs);
}
} else {
LOG.warning(String.format("unknown transform parameter: %s", key));
}
if (valueArgs.isEmpty()) {
valueArgs.add(factory.filter.literal(val));
}
switch (key) {
case "outputBBOX":
outputBBOX = paramExpression(key, valueArgs);
break;
case "outputWidth":
outputWidth = paramExpression(key, valueArgs);
break;
case "outputHeight":
outputHeight = paramExpression(key, valueArgs);
break;
default:
processArgs.add(paramExpression(key, valueArgs));
}
}
}
// If this process is the only one, and no input parameter was specified, use data by default
if (input == null && processes == 1) {
input = "data";
}
if (input != null) {
processArgs.add(paramExpression(input, Collections.<Expression> emptyList()));
}
if (outputBBOX != null) {
processArgs.add(outputBBOX);
}
if (outputWidth != null) {
processArgs.add(outputWidth);
}
if (outputHeight != null) {
processArgs.add(outputHeight);
}
Function function = functionFactory.function(processName(name), processArgs, null);
return function;
}
private Function paramExpression(String name, List<Expression> valueArgs) {
List<Expression> paramArgs = new ArrayList<Expression>(valueArgs.size() + 1);
paramArgs.add(factory.filter.literal(name));
paramArgs.addAll(valueArgs);
return factory.filter.function("parameter",
paramArgs.toArray(new Expression[paramArgs.size()]));
}
void convertAndAdd(Object val, Parameter<?> p, List<Expression> valueArgs) {
// handle collection case
if (p.getMaxOccurs() > 1 && val instanceof Collection) {
for (Object o : (Collection<?>) val) {
// just add directly
valueArgs.add(factory.filter.literal(o));
}
} else if (val instanceof Map) {
YamlMap map = YamlMap.<Map<?, ?>> create((Map<?, ?>) val).map();
valueArgs.add(process(map));
} else {
// just add directly
valueArgs.add(factory.filter.literal(val));
}
}
}