/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * Copyright (C) 2007-2008-2009 GeoSolutions S.A.S. * http://www.geo-solutions.it * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.sldservice.rest.resource; import java.awt.Color; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.transform.TransformerException; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.rest.AbstractCatalogResource; import org.geoserver.config.util.SecureXStream; import org.geoserver.rest.RestletException; import org.geoserver.rest.format.DataFormat; import org.geoserver.rest.format.ReflectiveHTMLFormat; import org.geoserver.rest.format.ReflectiveJSONFormat; import org.geoserver.rest.format.ReflectiveXMLFormat; import org.geoserver.sldservice.utils.classifier.ColorRamp; import org.geoserver.sldservice.utils.classifier.RulesBuilder; import org.geoserver.sldservice.utils.classifier.impl.BlueColorRamp; import org.geoserver.sldservice.utils.classifier.impl.CustomColorRamp; import org.geoserver.sldservice.utils.classifier.impl.GrayColorRamp; import org.geoserver.sldservice.utils.classifier.impl.JetColorRamp; import org.geoserver.sldservice.utils.classifier.impl.RandomColorRamp; import org.geoserver.sldservice.utils.classifier.impl.RedColorRamp; import org.geotools.feature.FeatureCollection; import org.geotools.styling.Rule; import org.geotools.styling.SLDTransformer; import org.geotools.util.NullProgressListener; import org.h2.bnf.RuleList; import org.opengis.feature.type.FeatureType; import org.restlet.Context; import org.restlet.data.Form; import org.restlet.data.Request; import org.restlet.data.Response; import org.restlet.data.Status; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; 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; import freemarker.ext.beans.CollectionModel; import freemarker.template.Configuration; import freemarker.template.SimpleHash; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import net.sf.json.xml.XMLSerializer; /** * @author Alessio Fabiani, GeoSolutions SAS */ public class ClassifierResource extends AbstractCatalogResource { private final static Logger LOGGER = Logger.getLogger(ClassifierResource.class.toString()); final private RulesBuilder builder = new RulesBuilder(); public ClassifierResource(Context context, Request request, Response response, Catalog catalog) { super(context, request, response, ClassifierResource.class, catalog); } @Override protected Object handleObjectGet() throws Exception { final Request req = getRequest(); final Map<String,Object> attributes = req.getAttributes(); final Form parameters = req.getResourceRef().getQueryAsForm(); final String layer = getAttribute("layer"); if (layer == null) { return new ArrayList(); } try { final LayerInfo layerInfo = catalog.getLayerByName(layer); if (layerInfo != null && layerInfo.getResource() instanceof FeatureTypeInfo) { final List<Rule> rules = this.generateClassifiedSLD(attributes, parameters); RulesList jsonRules = null; if (rules != null) jsonRules = generateRulesList(layer, getRequest(), rules); if (jsonRules != null) { return jsonRules; } else { throw new RestletException("Error generating Classification!", Status.CLIENT_ERROR_BAD_REQUEST); } } return new ArrayList(); } catch(IllegalArgumentException e) { return e.getMessage(); } } @Override public boolean allowPost() { return false; } @Override protected String handleObjectPost(Object object) { return null; } @Override protected void handleObjectPut(Object object) { // do nothing, we do not allow post } private RulesList generateRulesList(String layer, Request req, List<Rule> rules) { final RulesList ruleList = new RulesList(layer); for (Rule rule : rules) { ruleList.addRule(jsonRule(rule)); } return ruleList; } /** * */ @Override protected ReflectiveXMLFormat createXMLFormat(Request request, Response response) { return new ReflectiveXMLFormat() { @Override protected void write(Object data, OutputStream output) throws IOException { XStream xstream = new SecureXStream(); xstream.setMode(XStream.NO_REFERENCES); // Aliases xstream.alias("Rules", RulesList.class); // Converters xstream.registerConverter(new RulesListConverter()); xstream.allowTypes(new Class[] { RuleList.class }); // Marshalling xstream.toXML(data, output); } }; } /** * @see * org.geoserver.catalog.rest.AbstractCatalogResource#createJSONFormat(org * .restlet.data.Request, org.restlet.data.Response) */ @Override protected ReflectiveJSONFormat createJSONFormat(Request request, Response response) { return new ReflectiveJSONFormat() { /** * @see * org.geoserver.rest.format.ReflectiveJSONFormat#write(java.lang * .Object, java.io.OutputStream) */ @Override protected void write(Object data, OutputStream output) throws IOException { XStream xstream = new SecureXStream(new JettisonMappedXmlDriver()); xstream.setMode(XStream.NO_REFERENCES); // Aliases xstream.alias("Rules", RulesList.class); // Converters xstream.registerConverter(new RulesListConverter()); xstream.allowTypes(new Class[] { RuleList.class }); // Marshalling xstream.toXML(data, new OutputStreamWriter(output, "UTF-8")); } }; } /** * * @see * org.geoserver.catalog.rest.CatalogResourceBase#createHTMLFormat(org.restlet * .data.Request, org.restlet.data.Response) */ @Override protected DataFormat createHTMLFormat(Request request, Response response) { return new ReflectiveHTMLFormat(RulesList.class, request, response, this) { @Override protected Configuration createConfiguration(Object data, Class clazz) { final Configuration cfg = super.createConfiguration(data, clazz); cfg.setClassForTemplateLoading(getClass(), "templates"); cfg.setObjectWrapper(new ObjectToMapWrapper<RulesList>(RulesList.class) { @Override protected void wrapInternal(Map properties, SimpleHash model, RulesList object) { properties.put( "rules", new CollectionModel( object.rules, new ObjectToMapWrapper(RulesList.class) ) ); } }); return cfg; } }; } /** * * @param rule * @return a string with json Rule representation */ private JSONObject jsonRule(Rule rule) { JSONObject ruleSz = null; String xmlRule; XMLSerializer xmlS = new XMLSerializer(); SLDTransformer transform = new SLDTransformer(); transform.setIndentation(2); try { xmlRule = transform.transform(rule); xmlS.setRemoveNamespacePrefixFromElements(true); xmlS.setSkipNamespaces(true); ruleSz = (JSONObject) xmlS.read(xmlRule); } catch (TransformerException e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "Exception occurred while transformin the Rule " + e.getLocalizedMessage(),e); } return ruleSz; } private List<Rule> generateClassifiedSLD(Map<String,Object> attributes, Form form) { /* Looks in attribute map if there is the featureType param */ if (attributes.containsKey("layer")) { final String layerName = (String) attributes.get("layer"); final String property = form.getFirstValue("attribute"); final String method = form.getFirstValue("method", "equalInterval"); final String intervals = form.getFirstValue("intervals", "2"); final String intervalsForUnique = form.getFirstValue("intervals", "-1"); final String open = form.getFirstValue("open", "false"); final String colorRamp = form.getFirstValue("ramp", "red"); final boolean reverse = Boolean.parseBoolean(form.getFirstValue("reverse")); final boolean normalize = Boolean.parseBoolean(form.getFirstValue("normalize")); if (property != null && property.length() > 0) { /* First try to find as a FeatureType */ try { LayerInfo layerInfo = catalog.getLayerByName(layerName); if (layerInfo != null) { ResourceInfo obj = layerInfo.getResource(); /* Check if it's feature type or coverage */ if (obj instanceof FeatureTypeInfo) { final FeatureType ftType = ((FeatureTypeInfo) obj).getFeatureType(); final FeatureCollection ftCollection = ((FeatureTypeInfo) obj).getFeatureSource(new NullProgressListener(), null).getFeatures(); List<Rule> rules = null; Class<?> propertyType = ftType.getDescriptor(property).getType().getBinding(); if ("equalInterval".equals(method)) { rules = builder.equalIntervalClassification(ftCollection, property, propertyType, Integer.parseInt(intervals), Boolean.parseBoolean(open), normalize); } else if ("uniqueInterval".equals(method)) { rules = builder.uniqueIntervalClassification(ftCollection, property, propertyType, Integer.parseInt(intervalsForUnique), normalize); } else if ("quantile".equals(method)) { rules = builder.quantileClassification(ftCollection, property, propertyType, Integer.parseInt(intervals), Boolean.parseBoolean(open), normalize); } else if ("jenks".equals(method)) { rules = builder.jenksClassification(ftCollection, property, propertyType, Integer.parseInt(intervals), Boolean.parseBoolean(open), normalize); } if (colorRamp != null && colorRamp.length() > 0) { ColorRamp ramp = null; if (colorRamp.equalsIgnoreCase("random")) ramp = new RandomColorRamp(); else if (colorRamp.equalsIgnoreCase("red")) ramp = new RedColorRamp(); else if (colorRamp.equalsIgnoreCase("blue")) ramp = new BlueColorRamp(); else if (colorRamp.equalsIgnoreCase("jet")) ramp = new JetColorRamp(); else if (colorRamp.equalsIgnoreCase("gray")) ramp = new GrayColorRamp(); else if (colorRamp.equalsIgnoreCase("custom")) { Color startColor = Color.decode(form.getFirst("startColor").getValue()); Color endColor = Color.decode(form.getFirst("endColor").getValue()); Color midColor = (form.contains("midColor") ? Color.decode(form.getFirst("midColor").getValue()) : null); if (startColor != null && endColor != null) { CustomColorRamp tramp = new CustomColorRamp(); tramp.setStartColor(startColor); tramp.setEndColor(endColor); if (midColor != null) tramp.setMid(midColor); ramp = tramp; } } final Class geomT = ftType.getGeometryDescriptor().getType().getBinding(); /* * Line Symbolizer */ if (geomT == LineString.class || geomT == MultiLineString.class) { builder.lineStyle(rules, ramp, reverse); } /* * Point Symbolizer */ else if(geomT == Point.class || geomT == MultiPoint.class) { builder.pointStyle(rules, ramp, reverse); } /* * Polygon Symbolyzer */ else if (geomT == MultiPolygon.class || geomT == Polygon.class) { builder.polygonStyle(rules, ramp, reverse); } } return rules; } } } catch (NoSuchElementException e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE,"The following exception has occurred " + e.getLocalizedMessage(), e); return null; } catch (IOException e) { if (LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE, "The following exception has occurred " + e.getLocalizedMessage(), e); return null; } } else return null; } else return null; return null; } /** * * @author Fabiani * */ public class RulesList { private String layerName; private List<JSONObject> rules = new ArrayList<JSONObject>(); public RulesList(final String layer) { setLayerName(layer); } public void addRule(JSONObject object) { rules.add(object); } public List<JSONObject> getRules() { return rules; } /** * @param layerName the layerName to set */ public void setLayerName(String layerName) { this.layerName = layerName; } /** * @return the layerName */ public String getLayerName() { return layerName; } } /** * * @author Fabiani * */ public class RulesListConverter implements Converter { /** * @see * com.thoughtworks.xstream.converters.ConverterMatcher#canConvert(java * .lang.Class) */ public boolean canConvert(Class clazz) { return RulesList.class.isAssignableFrom(clazz); } /** * @see * com.thoughtworks.xstream.converters.Converter#marshal(java.lang.Object * , com.thoughtworks.xstream.io.HierarchicalStreamWriter, * com.thoughtworks.xstream.converters.MarshallingContext) */ public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { RulesList obj = (RulesList) value; for (JSONObject rule : obj.getRules()) { if (!rule.isEmpty() && !rule.isNullObject() && !rule.isArray()) { writer.startNode("Rule"); for (Object key : rule.keySet()) { writer.startNode((String) key); writeChild(writer, rule.get(key)); writer.endNode(); } writer.endNode(); } } } private void writeChild(HierarchicalStreamWriter writer, Object object) { if (object instanceof JSONObject && !((JSONObject) object).isArray()) { for (Object key : ((JSONObject) object).keySet()) { final Object obj = ((JSONObject) object).get(key); if (obj instanceof JSONArray) { for (int i = 0; i < ((JSONArray) obj).size(); i++) { final JSONObject child = (JSONObject) ((JSONArray) obj).get(i); writer.startNode((String) key); for (Object cKey : child.keySet()) { writeKey(writer, child, (String) cKey); } writer.endNode(); } } else { writeKey(writer, (JSONObject)object, (String)key); } } } else if (object instanceof JSONArray) { for (int i = 0; i < ((JSONArray) object).size(); i++) { final Object child = ((JSONArray) object).get(i); if (child instanceof JSONObject) { for (Object key : ((JSONObject) child).keySet()) { if (((JSONObject) child).get(key) instanceof String) writer.addAttribute((String) key, (String) ((JSONObject) child).get(key)); else writeChild(writer, ((JSONObject) child).get(key)); } } else { writeChild(writer, child); } } } else { writer.setValue(object.toString()); } } private void writeKey(HierarchicalStreamWriter writer, final JSONObject child, String key) { if (key.startsWith("@")) { writer.addAttribute(key.substring(1), (String) child.get(key)); } else if(key.startsWith("#")) { writer.setValue((String) child.get(key)); } else { writer.startNode(key); writeChild(writer, child.get(key)); writer.endNode(); } } /** * * @see com.thoughtworks.xstream.converters.Converter#unmarshal(com.thoughtworks.xstream.io. HierarchicalStreamReader,com.thoughtworks.xstream.converters.UnmarshallingContext) */ public Object unmarshal(HierarchicalStreamReader arg0, UnmarshallingContext arg1) { // TODO Auto-generated method stub return null; } } }