/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2011, Geomatys * * 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.geotoolkit.process.mapfile; import java.awt.Color; import java.util.List; import java.util.Collection; import java.io.IOException; import java.io.File; import java.util.ArrayList; import javax.xml.bind.JAXBException; import javax.measure.Unit; import org.geotoolkit.filter.DefaultFilterFactory2; import org.geotoolkit.processing.AbstractProcess; import org.geotoolkit.process.ProcessDescriptor; import org.geotoolkit.process.ProcessException; import org.geotoolkit.sld.DefaultSLDFactory; import org.geotoolkit.sld.MutableNamedLayer; import org.geotoolkit.sld.MutableSLDFactory; import org.geotoolkit.sld.MutableStyledLayerDescriptor; import org.geotoolkit.sld.xml.Specification.StyledLayerDescriptor; import org.geotoolkit.sld.xml.StyleXmlIO; import org.geotoolkit.style.DefaultStyleFactory; import org.geotoolkit.style.MutableFeatureTypeStyle; import org.geotoolkit.style.MutableRule; import org.geotoolkit.style.MutableStyle; import org.geotoolkit.style.MutableStyleFactory; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.style.PointSymbolizer; import org.opengis.style.Graphic; import org.opengis.style.Mark; import org.opengis.style.GraphicalSymbol; import org.opengis.style.AnchorPoint; import org.opengis.style.TextSymbolizer; import org.opengis.style.Font; import org.opengis.style.Halo; import org.opengis.style.LabelPlacement; import org.opengis.style.Description; import org.opengis.style.Displacement; import org.opengis.style.Fill; import org.opengis.style.PolygonSymbolizer; import org.opengis.style.Stroke; import org.opengis.style.LineSymbolizer; import org.opengis.style.Symbolizer; import org.opengis.filter.expression.Literal; import org.opengis.parameter.ParameterValueGroup; import org.apache.sis.measure.Units; import static org.geotoolkit.process.mapfile.MapfileToSLDDescriptor.*; import static org.geotoolkit.parameter.Parameters.*; import static org.geotoolkit.process.mapfile.MapfileTypes.*; import static org.geotoolkit.style.StyleConstants.*; import org.opengis.feature.Feature; /** * @author Johann Sorel (Geomatys) * @module */ public class MapfileToSLDProcess extends AbstractProcess{ private static final MutableSLDFactory SLDF = new DefaultSLDFactory(); private static final MutableStyleFactory SF = new DefaultStyleFactory(); private static final FilterFactory FF = new DefaultFilterFactory2(); private Feature mapfileFeature = null; public MapfileToSLDProcess(final ParameterValueGroup input){ super(INSTANCE, input); } @Override protected void execute() throws ProcessException { final File mapfile = value(IN_FILE, inputParameters); final File sldfile = value(IN_OUTPUT, inputParameters); final MapfileReader reader = new MapfileReader(); reader.setInput(mapfile); try { mapfileFeature = reader.read(); final MutableStyledLayerDescriptor sld = SLDF.createSLD(); //convert it convert(sld, mapfileFeature); //avoid memory leak mapfileFeature = null; //write the sld final StyleXmlIO utils = new StyleXmlIO(); utils.writeSLD(sldfile, sld, StyledLayerDescriptor.V_1_1_0); } catch (IOException ex) { throw new ProcessException(ex.getMessage(), this, ex); } catch (JAXBException ex) { throw new ProcessException(ex.getMessage(), this, ex); } } private void convert(final MutableStyledLayerDescriptor sld, final Feature feature) throws ProcessException{ final Collection<Feature> layers = (Collection<Feature>) feature.getPropertyValue(MAP_LAYER.toString()); for(final Feature mflayer : layers){ //create an sld layer final MutableNamedLayer sldLayer = SLDF.createNamedLayer(); sld.layers().add(sldLayer); final String name = String.valueOf(mflayer.getPropertyValue(LAYER_NAME.toString())); sldLayer.setName(name); sldLayer.setDescription(SF.description(name, name)); //create the style final MutableStyle sldStyle = SF.style(); sldLayer.styles().add(sldStyle); final MutableFeatureTypeStyle fts = SF.featureTypeStyle(); sldStyle.featureTypeStyles().add(fts); final Double minscale = (Double)mflayer.getPropertyValue(LAYER_MINSCALEDENOM.toString()); final Double maxscale = (Double)mflayer.getPropertyValue(LAYER_MAXSCALEDENOM.toString()); final Collection<Feature> classes = (Collection<Feature>) mflayer.getPropertyValue(LAYER_CLASS.toString()); for(final Feature clazz : classes){ final MutableRule rule = createRule(mflayer, minscale, maxscale, clazz); fts.rules().add(rule); } } } private MutableRule createRule(final Feature mflayer, final Double minScale, final Double maxscale, final Feature clazz) throws ProcessException{ //mapfile type is similar to se symbolizer type final String type = (String) mflayer.getPropertyValue(LAYER_TYPE.toString()); final MutableRule rule = SF.rule(); final StringBuilder name = new StringBuilder("["); if(minScale != null){ rule.setMinScaleDenominator(minScale); name.append(minScale); }else{ name.append(0); } name.append(" ↔ "); if(maxscale != null){ rule.setMaxScaleDenominator(maxscale); name.append(maxscale); }else{ name.append(Double.POSITIVE_INFINITY); } name.append("]"); rule.setDescription(SF.description(name.toString(), name.toString())); // Class can act as filter, the classItem is the propertyname on which the class // Expression is evaluated final PropertyName classItem = (PropertyName) mflayer.getPropertyValue(LAYER_CLASSITEM.toString()); final String classExpression = (String) clazz.getPropertyValue(CLASS_EXPRESSION.toString()); if(classExpression != null){ // equivalant to OGC filter : PropertyEquals(name,value) final Filter filter = toFilter(classItem, classExpression); rule.setFilter(filter); }else{ //filter //not handle yet rule.setElseFilter(true); } final Collection<Feature> styles = (Collection<Feature>) clazz.getPropertyValue(CLASS_STYLE.toString()); final Collection<Feature> labels = (Collection<Feature>) clazz.getPropertyValue(CLASS_LABEL.toString()); for(final Feature style : styles){ if("POLYGON".equalsIgnoreCase(type)){ rule.symbolizers().addAll(createPolygonSymbolizer(style)); }else if("LINE".equalsIgnoreCase(type)){ rule.symbolizers().addAll(createLineSymbolizer(style)); }else if("ANNOTATION".equalsIgnoreCase(type)){ rule.symbolizers().addAll(createPointSymbolizer(style)); } } for(final Feature label : labels){ //this property contain the label to place in the text symbolizer Expression labelProp = (Expression) mflayer.getPropertyValue(LAYER_LABELITEM.toString()); Expression labelOverride = (Expression) mflayer.getPropertyValue(CLASS_TEXT.toString()); if(labelProp == null || labelOverride != null){ //Class Text take priority over label item labelProp = labelOverride; } rule.symbolizers().addAll(createTextSymbolizer(labelProp,label)); } return rule; } private List<Symbolizer> createPolygonSymbolizer(final Feature style){ Expression expColor = (Expression) style.getPropertyValue(STYLE_COLOR.toString()); Expression expOpacity = (Expression) style.getPropertyValue(STYLE_OPACITY.toString()); if(expOpacity == null){ expOpacity = DEFAULT_FILL_OPACITY; }else{ //mapfile opacity is expressed in %, SE is in 0-1 if(expOpacity instanceof Literal){ Double d= expOpacity.evaluate(null, Double.class); d /= 100d; expOpacity = FF.literal(d); }else{ expOpacity = FF.divide(expOpacity, FF.literal(100)); } } if(expColor == null){ expColor = DEFAULT_FILL_COLOR; } Fill fill = null; Stroke stroke = null; fill = SF.fill(expColor,expOpacity); final List<Symbolizer> symbolizers = new ArrayList<Symbolizer>(); //general informations final String name = ""; final Description desc = DEFAULT_DESCRIPTION; final String geometry = null; //use the default geometry of the feature final Unit unit = Units.POINT; final Displacement disp = DEFAULT_DISPLACEMENT; final Expression offset = LITERAL_ZERO_FLOAT; //stroke element //final Expression color = SF.literal(Color.BLUE); //final Expression width = FF.literal(4); //final Expression opacity = LITERAL_ONE_FLOAT; //final Stroke stroke = SF.stroke(color,width,opacity); final PolygonSymbolizer symbolizer = SF.polygonSymbolizer(name,geometry,desc,unit,stroke,fill,disp,offset); symbolizers.add(symbolizer); return symbolizers; } private List<Symbolizer> createLineSymbolizer(final Feature style){ Expression expColor = (Expression) style.getPropertyValue(STYLE_COLOR.toString()); Expression expWidth = (Expression) style.getPropertyValue(STYLE_WIDTH.toString()); Expression expOpacity = (Expression) style.getPropertyValue(STYLE_OPACITY.toString()); float[] dashes = (float[]) style.getPropertyValue(STYLE_PATTERN.toString()); Literal explinecap = (Literal) style.getPropertyValue(STYLE_LINECAP.toString()); Literal explinejoin = (Literal) style.getPropertyValue(STYLE_LINEJOIN.toString()); Expression expOutlineColor = (Expression) style.getPropertyValue(STYLE_OUTLINECOLOR.toString()); Expression expOutlineWidth = (Expression) style.getPropertyValue(STYLE_OUTLINEWIDTH.toString()); if(expOpacity == null){ expOpacity = DEFAULT_STROKE_OPACITY; }else{ //mapfile opacity is expressed in %, SE is in 0-1 if(expOpacity instanceof Literal){ Double d= expOpacity.evaluate(null, Double.class); d /= 100d; expOpacity = FF.literal(d); }else{ expOpacity = FF.divide(expOpacity, FF.literal(100)); } } if(expWidth == null){ expWidth = DEFAULT_STROKE_WIDTH; } if(explinecap == null){ explinecap = STROKE_CAP_ROUND; } if(explinejoin == null){ explinejoin = DEFAULT_STROKE_JOIN; }else{ //mapfile write 'miter' not 'mitre' like in sld/se if("miter".equalsIgnoreCase(String.valueOf(explinejoin.getValue()))){ explinejoin = STROKE_JOIN_MITRE; } } final List<Symbolizer> symbolizers = new ArrayList<>(); //Check if it's an outline //Mapfile outline , is similar to line symbolizer placed under the main one //this produce a line border effect if(expOutlineColor != null && expOutlineWidth != null){ final Expression width = FF.add(expWidth, FF.multiply(expOutlineWidth,FF.literal(2))); final Stroke stroke = SF.stroke(expOutlineColor,expOpacity,width,explinejoin,explinecap,null,LITERAL_ZERO_FLOAT); final LineSymbolizer outline = SF.lineSymbolizer( "",(String)null,DEFAULT_DESCRIPTION,Units.POINT,stroke,LITERAL_ZERO_FLOAT); symbolizers.add(outline); } if(expColor != null){ //general informations final String name = ""; final Description desc = DEFAULT_DESCRIPTION; final String geometry = null; //use the default geometry of the feature final Unit unit = Units.POINT; final Expression offset = LITERAL_ZERO_FLOAT; //the visual element final Expression dashOffset = LITERAL_ZERO_FLOAT; final Stroke stroke = SF.stroke(expColor,expOpacity,expWidth,explinejoin,explinecap,dashes,dashOffset); final LineSymbolizer symbolizer = SF.lineSymbolizer(name,geometry,desc,unit,stroke,offset); symbolizers.add(symbolizer); } return symbolizers; } private List<Symbolizer> createTextSymbolizer(final Expression label, final Feature lblStyle){ Expression expLabelColor = (Expression) lblStyle.getPropertyValue(LABEL_COLOR.toString()); Expression expLabelSize = (Expression) lblStyle.getPropertyValue(LABEL_SIZE.toString()); Expression expHaloColor = (Expression) lblStyle.getPropertyValue(LABEL_OUTLINECOLOR.toString()); Integer valHaloWidth = (Integer) lblStyle.getPropertyValue(LABEL_OUTLINEWIDTH.toString()); String valAngle = (String) lblStyle.getPropertyValue(LABEL_ANGLE.toString()); if(expLabelColor == null){ expLabelColor = SF.literal(Color.BLACK); } if(expLabelSize == null){ expLabelSize = DEFAULT_FONT_SIZE; } if(expHaloColor == null){ expHaloColor = SF.literal(Color.WHITE); } if(valHaloWidth == null){ valHaloWidth = 0; } Expression expHaloWidth = FF.literal(valHaloWidth); final List<Symbolizer> symbolizers = new ArrayList<Symbolizer>(); LabelPlacement placement = SF.pointPlacement(); if(valAngle != null){ if("FOLLOW".equalsIgnoreCase(valAngle) || "AUTO".equalsIgnoreCase(valAngle)){ final Expression offset = FF.divide(expLabelSize, FF.literal(-2)); final Expression initial = FF.literal(20); Expression gap = LITERAL_ZERO_FLOAT; boolean repeated = false; final boolean aligned = false; final boolean generalize = false; Integer minDistance = (Integer) lblStyle.getPropertyValue(LABEL_MINDISTANCE.toString()); if(minDistance != null){ repeated = true; gap = FF.literal(minDistance); } placement = SF.linePlacement(offset,initial,gap,repeated,aligned,generalize); }else{ Expression rotation = LITERAL_ZERO_FLOAT; //try if it's a number try{ double d = Double.valueOf(valAngle); rotation = FF.literal(d); }catch(Exception ex){ } placement = SF.pointPlacement(DEFAULT_ANCHOR_POINT, DEFAULT_DISPLACEMENT, rotation); } } //general informations final String name = ""; final Description desc = DEFAULT_DESCRIPTION; final String geometry = null; //use the default geometry of the feature final Unit unit = Units.POINT; final Font font = SF.font( FF.literal("Arial"), FONT_STYLE_NORMAL, FONT_WEIGHT_NORMAL, expLabelSize); final Halo halo = SF.halo(SF.fill(expHaloColor), expHaloWidth); final Fill fill = SF.fill(expLabelColor); final TextSymbolizer symbol = SF.textSymbolizer(name, geometry, desc, unit, label, font, placement, halo, fill); symbolizers.add(symbol); return symbolizers; } private List<Symbolizer> createPointSymbolizer(final Feature style){ final String symbolName = (String) style.getPropertyValue(STYLE_SYMBOL.toString()); Expression expSize = (Expression) style.getPropertyValue(STYLE_SIZE.toString()); Expression expOpacity = (Expression) style.getPropertyValue(STYLE_OPACITY.toString()); Expression expFillColor = (Expression) style.getPropertyValue(STYLE_COLOR.toString()); Expression expStrokeColor = (Expression) style.getPropertyValue(STYLE_OUTLINECOLOR.toString()); Expression expStrokeWidth = (Expression) style.getPropertyValue(STYLE_WIDTH.toString()); if(expFillColor == null){ expFillColor = DEFAULT_FILL_COLOR; } if(expStrokeColor == null){ expStrokeColor = DEFAULT_STROKE_COLOR; } if(expStrokeWidth == null){ expStrokeWidth = FF.literal(0); } if(expOpacity == null){ expOpacity = DEFAULT_GRAPHIC_OPACITY; } if(expSize == null){ expSize = DEFAULT_GRAPHIC_SIZE; } final List<Symbolizer> symbolizers = new ArrayList<Symbolizer>(); final Feature symbol = getSymbol(symbolName); if(symbol == null){ //no symbol found for this name return symbolizers; } final Stroke stroke = SF.stroke(expStrokeColor, expStrokeWidth); final Fill fill = SF.fill(expFillColor); final String symbolTypeName = (String) symbol.getPropertyValue(SYMBOL_TYPE.toString()); final Mark mark; if("ellipse".equals(symbolTypeName)){ mark = SF.mark(MARK_CIRCLE, fill, stroke); }else if("hatch".equals(symbolTypeName)){ //TODO mark = SF.mark(MARK_SQUARE, fill, stroke); }else if("pixmap".equals(symbolTypeName)){ //TODO mark = SF.mark(MARK_SQUARE, fill, stroke); }else if("simple".equals(symbolTypeName)){ //TODO mark = SF.mark(MARK_SQUARE, fill, stroke); }else if("truetype".equals(symbolTypeName)){ //TODO mark = SF.mark(MARK_SQUARE, fill, stroke); }else if("vector".equals(symbolTypeName)){ //TODO mark = SF.mark(MARK_SQUARE, fill, stroke); }else{ //can not build symbol return symbolizers; } //general informations final String name = ""; final Description desc = DEFAULT_DESCRIPTION; final String geometry = null; //use the default geometry of the feature final Unit unit = Units.POINT; //the visual element final Expression opacity = LITERAL_ONE_FLOAT; final Expression rotation = LITERAL_ZERO_FLOAT; final AnchorPoint anchor = DEFAULT_ANCHOR_POINT; final Displacement disp = DEFAULT_DISPLACEMENT; final List<GraphicalSymbol> symbols = new ArrayList<GraphicalSymbol>(); symbols.add(mark); final Graphic graphic = SF.graphic(symbols, opacity, expSize, rotation, anchor, disp); final PointSymbolizer symbolizer = SF.pointSymbolizer(name,geometry,desc,unit, graphic); symbolizers.add(symbolizer); return symbolizers; } /** * * @param name : symbol name * @return the symbol which has the given name */ private Feature getSymbol(final String name){ if(name == null){ return null; } final Collection<Feature> symbols = (Collection<Feature>) mapfileFeature.getPropertyValue(MAP_SYMBOL.toString()); for(final Feature ca : symbols){ if(name.equals(ca.getPropertyValue(SYMBOL_NAME.toString()))){ return ca; } } return null; } private static Filter toFilter(final Expression ref, final String text) throws ProcessException{ final ProcessDescriptor desc = MapfileFilterToOGCFilterDescriptor.INSTANCE; final ParameterValueGroup input = desc.getInputDescriptor().createValue(); getOrCreate(MapfileFilterToOGCFilterDescriptor.IN_TEXT, input).setValue(text); getOrCreate(MapfileFilterToOGCFilterDescriptor.IN_REFERENCE, input).setValue(ref); final org.geotoolkit.process.Process process = desc.createProcess(input); final ParameterValueGroup output = process.call(); final Filter result = (Filter) value(MapfileFilterToOGCFilterDescriptor.OUT_OGC, output); return result; } }