All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geomajas.internal.service.StyleConverterServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
 *
 * Copyright 2008-2016 Geosparc nv, http://www.geosparc.com/, Belgium.
 *
 * The program is available in open source according to the GNU Affero
 * General Public License. All contributions in this program are covered
 * by the Geomajas Contributors License Agreement. For full licensing
 * details, see LICENSE.txt in the project root.
 */
package org.geomajas.internal.service;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;

import org.geomajas.configuration.AbstractAttributeInfo;
import org.geomajas.configuration.CircleInfo;
import org.geomajas.configuration.FeatureInfo;
import org.geomajas.configuration.FeatureStyleInfo;
import org.geomajas.configuration.FontStyleInfo;
import org.geomajas.configuration.ImageInfo;
import org.geomajas.configuration.LabelStyleInfo;
import org.geomajas.configuration.NamedStyleInfo;
import org.geomajas.configuration.PrimitiveAttributeInfo;
import org.geomajas.configuration.PrimitiveType;
import org.geomajas.configuration.RectInfo;
import org.geomajas.configuration.SymbolInfo;
import org.geomajas.global.ExceptionCode;
import org.geomajas.global.GeomajasConstant;
import org.geomajas.global.GeomajasException;
import org.geomajas.layer.LayerException;
import org.geomajas.layer.LayerType;
import org.geomajas.service.FilterService;
import org.geomajas.service.ResourceService;
import org.geomajas.service.StyleConverterService;
import org.geomajas.sld.CssParameterInfo;
import org.geomajas.sld.ExternalGraphicInfo;
import org.geomajas.sld.FeatureTypeStyleInfo;
import org.geomajas.sld.FillInfo;
import org.geomajas.sld.FontInfo;
import org.geomajas.sld.GraphicInfo;
import org.geomajas.sld.GraphicInfo.ChoiceInfo;
import org.geomajas.sld.LineSymbolizerInfo;
import org.geomajas.sld.MarkInfo;
import org.geomajas.sld.NamedLayerInfo;
import org.geomajas.sld.ParameterValueTypeInfo;
import org.geomajas.sld.PointSymbolizerInfo;
import org.geomajas.sld.PolygonSymbolizerInfo;
import org.geomajas.sld.RuleInfo;
import org.geomajas.sld.StrokeInfo;
import org.geomajas.sld.StyledLayerDescriptorInfo;
import org.geomajas.sld.SymbolizerTypeInfo;
import org.geomajas.sld.TextSymbolizerInfo;
import org.geomajas.sld.UserStyleInfo;
import org.geomajas.sld.expression.ExpressionInfo;
import org.geomajas.sld.expression.LiteralTypeInfo;
import org.geomajas.sld.filter.AndInfo;
import org.geomajas.sld.filter.BboxTypeInfo;
import org.geomajas.sld.filter.BeyondInfo;
import org.geomajas.sld.filter.BinaryComparisonOpTypeInfo;
import org.geomajas.sld.filter.BinaryLogicOpTypeInfo;
import org.geomajas.sld.filter.BinarySpatialOpTypeInfo;
import org.geomajas.sld.filter.ComparisonOpsTypeInfo;
import org.geomajas.sld.filter.ContainsInfo;
import org.geomajas.sld.filter.CrossesInfo;
import org.geomajas.sld.filter.DWithinInfo;
import org.geomajas.sld.filter.DisjointInfo;
import org.geomajas.sld.filter.DistanceBufferTypeInfo;
import org.geomajas.sld.filter.EqualsInfo;
import org.geomajas.sld.filter.FeatureIdTypeInfo;
import org.geomajas.sld.filter.FilterTypeInfo;
import org.geomajas.sld.filter.IntersectsInfo;
import org.geomajas.sld.filter.LogicOpsTypeInfo;
import org.geomajas.sld.filter.OrInfo;
import org.geomajas.sld.filter.OverlapsInfo;
import org.geomajas.sld.filter.PropertyIsBetweenTypeInfo;
import org.geomajas.sld.filter.PropertyIsEqualToInfo;
import org.geomajas.sld.filter.PropertyIsGreaterThanInfo;
import org.geomajas.sld.filter.PropertyIsGreaterThanOrEqualToInfo;
import org.geomajas.sld.filter.PropertyIsLessThanInfo;
import org.geomajas.sld.filter.PropertyIsLessThanOrEqualToInfo;
import org.geomajas.sld.filter.PropertyIsLikeTypeInfo;
import org.geomajas.sld.filter.PropertyIsNotEqualToInfo;
import org.geomajas.sld.filter.PropertyIsNullTypeInfo;
import org.geomajas.sld.filter.SpatialOpsTypeInfo;
import org.geomajas.sld.filter.TouchesInfo;
import org.geomajas.sld.filter.UnaryLogicOpTypeInfo;
import org.geomajas.sld.filter.WithinInfo;
import org.geomajas.sld.geometry.AbstractGeometryCollectionInfo;
import org.geomajas.sld.geometry.AbstractGeometryInfo;
import org.geomajas.sld.geometry.BoxTypeInfo;
import org.geomajas.sld.geometry.CoordTypeInfo;
import org.geomajas.sld.geometry.CoordinatesTypeInfo;
import org.geomajas.sld.geometry.GeometryMemberInfo;
import org.geomajas.sld.geometry.InnerBoundaryIsInfo;
import org.geomajas.sld.geometry.LineStringTypeInfo;
import org.geomajas.sld.geometry.LinearRingTypeInfo;
import org.geomajas.sld.geometry.MultiGeometryInfo;
import org.geomajas.sld.geometry.MultiLineStringInfo;
import org.geomajas.sld.geometry.MultiPointInfo;
import org.geomajas.sld.geometry.MultiPolygonInfo;
import org.geomajas.sld.geometry.OuterBoundaryIsInfo;
import org.geomajas.sld.geometry.PointTypeInfo;
import org.geomajas.sld.geometry.PolygonTypeInfo;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.styling.FeatureTypeConstraint;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.ResourceLocator;
import org.geotools.styling.Rule;
import org.geotools.styling.SLDParser;
import org.geotools.styling.SLDTransformer;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleBuilder;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.StyledLayerDescriptor;
import org.geotools.styling.Symbolizer;
import org.geotools.styling.TextSymbolizer;
import org.geotools.styling.UserLayer;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshallingContext;
import org.jibx.runtime.IUnmarshallingContext;
import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
import org.opengis.style.GraphicalSymbol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTWriter;

/**
 * Default implementation of {@link StyleConverterService}. Supports named layers and user styles only.
 * 
 * @author Jan De Moerloose
 */
@Component
public class StyleConverterServiceImpl implements StyleConverterService {

	private static final String SLD_VERSION = "1.0.0";

	private static final String DUMMY_NAMED_LAYER = "Dummy";

	private static final String CSS_FILL = "fill";
	private static final String CSS_FILL_OPACITY = "fill-opacity";
	private static final String CSS_STROKE = "stroke";
	private static final String CSS_STROKE_OPACITY = "stroke-opacity";
	private static final String CSS_STROKE_WIDTH = "stroke-width";
	private static final String CSS_STROKE_DASH_ARRAY = "stroke-dasharray";
	private static final String CSS_FONT_SIZE = "font-size";
	private static final String CSS_FONT_STYLE = "font-style";
	private static final String CSS_FONT_WEIGHT = "font-weight";
	private static final String CSS_FONT_FAMILY = "font-family";

	private static final String MARK_SQUARE = "square";
	private static final String MARK_CIRCLE = "circle";
	
	private static final String ASSOCIATION_DELIMITER_SOURCE = "/";
	private static final String ASSOCIATION_DELIMITER_TARGET = ".";
	
	private static final String MISSING_RESOURCE = "missing resource {}";

	private StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);

	private StyleBuilder styleBuilder;

	private final Logger log = LoggerFactory.getLogger(StyleConverterServiceImpl.class);

	@Autowired
	private ApplicationContext applicationContext;

	@Autowired
	private FilterService filterService;

	@Autowired
	private ResourceService resourceService;

	@Override
	public NamedStyleInfo convert(UserStyleInfo userStyle, FeatureInfo featureInfo) throws LayerException {
		NamedStyleInfo namedStyleInfo = new NamedStyleInfo();
		LabelStyleInfo labelStyleInfo = new LabelStyleInfo();
		List featureStyleInfos = new ArrayList();
		for (FeatureTypeStyleInfo featureTypeStyleInfo : userStyle.getFeatureTypeStyleList()) {
			int styleIndex = 0;
			for (RuleInfo ruleInfo : featureTypeStyleInfo.getRuleList()) {
				FeatureStyleInfo featureStyleInfo = new FeatureStyleInfo();
				if (null != ruleInfo.getChoice()  && ruleInfo.getChoice().ifFilter()) {
					featureStyleInfo.setFormula(convertFormula(ruleInfo.getChoice().getFilter(), featureInfo));
				}
				for (SymbolizerTypeInfo symbolizerTypeInfo : ruleInfo.getSymbolizerList()) {
					if (symbolizerTypeInfo instanceof PointSymbolizerInfo) {
						PointSymbolizerInfo pointInfo = (PointSymbolizerInfo) symbolizerTypeInfo;
						convertSymbol(featureStyleInfo, pointInfo);
					} else if (symbolizerTypeInfo instanceof LineSymbolizerInfo) {
						LineSymbolizerInfo lineInfo = (LineSymbolizerInfo) symbolizerTypeInfo;
						convertStroke(featureStyleInfo, lineInfo.getStroke());
					} else if (symbolizerTypeInfo instanceof PolygonSymbolizerInfo) {
						PolygonSymbolizerInfo polygonInfo = (PolygonSymbolizerInfo) symbolizerTypeInfo;
						convertFill(featureStyleInfo, polygonInfo.getFill());
						convertStroke(featureStyleInfo, polygonInfo.getStroke());
					} else if (symbolizerTypeInfo instanceof TextSymbolizerInfo) {
						TextSymbolizerInfo textInfo = (TextSymbolizerInfo) symbolizerTypeInfo;
						labelStyleInfo.setFontStyle(convertFont(textInfo.getFont()));
						for (ExpressionInfo expr : textInfo.getLabel().getExpressionList()) {
							if (expr instanceof LiteralTypeInfo ) {
								String literalValue = expr.getValue();
								labelStyleInfo.setLabelValueExpression("'" + literalValue + "'");
							} else { // Assume expr is of class PropertyNameInfo
								labelStyleInfo.setLabelAttributeName(expr.getValue());
							}
						}
						convertFontFill(labelStyleInfo.getFontStyle(), textInfo.getFill());
						FeatureStyleInfo background = new FeatureStyleInfo();
						if (textInfo.getHalo() != null) {
							convertFill(background, textInfo.getHalo().getFill());
						}
						labelStyleInfo.setBackgroundStyle(background);
					} else {
						log.warn("Symbolizer type " + symbolizerTypeInfo
								+ "cannot be converted to feature style info, reverting to default style");
					}
				}
				if (featureStyleInfo.getStrokeColor() == null && featureStyleInfo.getFillColor() != null) {
					// avoid default stroke by setting invisible
					featureStyleInfo.setStrokeColor("black");
					featureStyleInfo.setStrokeOpacity(0);
					featureStyleInfo.setStrokeWidth(0);
				}
				featureStyleInfo.setIndex(styleIndex++);
				featureStyleInfo.setName(ruleInfo.getTitle() != null ? ruleInfo.getTitle() : ruleInfo.getName());
				featureStyleInfos.add(featureStyleInfo);
			}
		}
		namedStyleInfo.setName(userStyle.getTitle() != null ? userStyle.getTitle() : userStyle.getName());
		namedStyleInfo.setFeatureStyles(featureStyleInfos);
		namedStyleInfo.setLabelStyle(labelStyleInfo);
		return namedStyleInfo;
	}

	@Override
	public Style convert(UserStyleInfo userStyleInfo) throws LayerException {
		IBindingFactory bindingFactory;
		try {
			// create a dummy SLD root
			StyledLayerDescriptorInfo sld = new StyledLayerDescriptorInfo();
			sld.setVersion(SLD_VERSION);
			StyledLayerDescriptorInfo.ChoiceInfo choice = new StyledLayerDescriptorInfo.ChoiceInfo();
			NamedLayerInfo namedLayerInfo = new NamedLayerInfo();
			namedLayerInfo.setName(DUMMY_NAMED_LAYER);
			NamedLayerInfo.ChoiceInfo userChoice = new NamedLayerInfo.ChoiceInfo();
			userChoice.setUserStyle(userStyleInfo);
			namedLayerInfo.getChoiceList().add(userChoice);
			choice.setNamedLayer(namedLayerInfo);
			sld.getChoiceList().add(choice);

			// force through Geotools parser
			bindingFactory = BindingDirectory.getFactory(StyledLayerDescriptorInfo.class);
			IMarshallingContext marshallingContext = bindingFactory.createMarshallingContext();
			StringWriter sw = new StringWriter();
			marshallingContext.setOutput(sw);
			marshallingContext.marshalDocument(sld);

			SLDParser parser = new SLDParser(styleFactory, filterService.getFilterFactory());
			parser.setOnLineResourceLocator(new ResourceServiceBasedLocator());
			parser.setInput(new StringReader(sw.toString()));

			Style[] styles = parser.readXML();
			if (styles.length != 0) {
				return styles[0];
			} else {
				throw new LayerException(ExceptionCode.INVALID_USER_STYLE, userStyleInfo.getName());
			}
		} catch (Exception e) {
			throw new LayerException(e, ExceptionCode.INVALID_USER_STYLE, userStyleInfo.getName());
		}
	}

	@Override
	public Rule convert(RuleInfo ruleInfo) throws LayerException {
		UserStyleInfo styleInfo = new UserStyleInfo();
		FeatureTypeStyleInfo fts = new FeatureTypeStyleInfo();
		fts.getRuleList().add(ruleInfo);
		styleInfo.getFeatureTypeStyleList().add(fts);
		Style style = convert(styleInfo);
		return style.featureTypeStyles().get(0).rules().get(0);
	}

	@Override
	public UserStyleInfo convert(NamedStyleInfo namedStyleInfo, String geometryName) throws LayerException {
		Style style = styleBuilder.createStyle();

		// list of rules
		List rules = new ArrayList();
		for (FeatureStyleInfo featureStyle : namedStyleInfo.getFeatureStyles()) {
			// create the filter
			Filter styleFilter;
			if (featureStyle.getFormula() != null && featureStyle.getFormula().length() > 0) {
				try {
					styleFilter = filterService.parseFilter(featureStyle.getFormula());
				} catch (GeomajasException e) {
					throw new LayerException(e, ExceptionCode.FILTER_PARSE_PROBLEM, featureStyle.getFormula());
				}
			} else {
				styleFilter = Filter.INCLUDE;
			}
			Rule rule = createRule(styleFilter, featureStyle);
			// add the label symbolizer to the rule
			TextSymbolizer textSymbolizer = createTextSymbolizer(namedStyleInfo.getLabelStyle(),
					featureStyle.getLayerType());
			rule.symbolizers().add(textSymbolizer);
			rules.add(rule);
		}
		// create the style
		FeatureTypeStyle fts = styleBuilder.createFeatureTypeStyle(null, rules.toArray(new Rule[rules.size()]));
		style.featureTypeStyles().add(fts);
		// parse and to dto

		try {
			StyledLayerDescriptor styledLayerDescriptor = styleFactory.createStyledLayerDescriptor();
			UserLayer layer = styleFactory.createUserLayer();
			layer.setLayerFeatureConstraints(new FeatureTypeConstraint[] { null });
			styledLayerDescriptor.addStyledLayer(layer);
			layer.addUserStyle(style);

			SLDTransformer styleTransform = new SLDTransformer();
			String xml = styleTransform.transform(styledLayerDescriptor);
			IBindingFactory bindingFactory = BindingDirectory.getFactory(StyledLayerDescriptorInfo.class);
			IUnmarshallingContext unmarshallingContext = bindingFactory.createUnmarshallingContext();
			StyledLayerDescriptorInfo sld = (StyledLayerDescriptorInfo) unmarshallingContext
					.unmarshalDocument(new StringReader(xml));
			return sld.getChoiceList().get(0).getUserLayer().getUserStyleList().get(0);
		} catch (Exception e) {
			throw new LayerException(e, ExceptionCode.INVALID_USER_STYLE, namedStyleInfo.getName());
		}
	}

	private void convertSymbol(FeatureStyleInfo featureStyleInfo, PointSymbolizerInfo pointInfo)
			throws LayerException {
		GraphicInfo graphic = pointInfo.getGraphic();
		SymbolInfo symbol = new SymbolInfo();

		if (graphic.getChoiceList().size() > 0) {
			ChoiceInfo choice = graphic.getChoiceList().get(0);
			if (choice.ifExternalGraphic()) {
				ExternalGraphicInfo externalGraphic = choice.getExternalGraphic();
				String href = externalGraphic.getOnlineResource().getHref().getHref();
				ImageInfo image = new ImageInfo();
				image.setHref(href);
				// SLD has no selection concept + no default: what to do ?
				image.setSelectionHref(href);
				try {
					BufferedImage img = ImageIO.read(resourceService.find(href).getInputStream());
					image.setWidth(img.getWidth());
					image.setHeight(img.getHeight());
				} catch (Exception e) {
					// cannot determine image
					log.warn("Unable to determine size of image " + href, e);
				}
				if (graphic.getSize() != null) {
					double scale = parseFloat(getParameterValue(graphic.getSize())) / image.getHeight();
					image.setHeight((int) (scale * image.getHeight()));
					image.setWidth((int) (scale * image.getWidth()));
				}
				symbol.setImage(image);
			} else if (choice.ifMark()) {
				MarkInfo mark = choice.getMark();
				String name = mark.getWellKnownName().getWellKnownName();
				if (MARK_SQUARE.equalsIgnoreCase(name)) {
					RectInfo rect = new RectInfo();
					rect.setH(parseFloat(getParameterValue(graphic.getSize())));
					rect.setW(parseFloat(getParameterValue(graphic.getSize())));
					symbol.setRect(rect);
				} else {
					// should treat everything else as circle ?!
					CircleInfo circle = new CircleInfo();
					circle.setR(0.5F * parseFloat(getParameterValue(graphic.getSize())));
					symbol.setCircle(circle);
				}
				convertFill(featureStyleInfo, mark.getFill());
				convertStroke(featureStyleInfo, mark.getStroke());
			}
		}
		featureStyleInfo.setSymbol(symbol);
	}

	private float parseFloat(String str) throws LayerException {
		try {
			return Float.parseFloat(str);
		} catch (NumberFormatException nfe) {
			throw new LayerException(nfe, ExceptionCode.SLD_PARSE_NUMBER, str);
		}
	}

	private double parseDouble(String str) throws LayerException {
		try {
			return Double.parseDouble(str);
		} catch (NumberFormatException nfe) {
			throw new LayerException(nfe, ExceptionCode.SLD_PARSE_NUMBER, str);
		}
	}

	private float parseFloat(Map cssMap, String attribute) throws LayerException {
		String str = cssMap.get(attribute);
		try {
			return Float.parseFloat(str);
		} catch (NumberFormatException nfe) {
			throw new LayerException(nfe, ExceptionCode.SLD_PARSE_NUMBER_ATTRIBUTE, str, attribute);
		}
	}

	private void convertFontFill(FontStyleInfo fontStyle, FillInfo fill) throws LayerException {
		if (fill != null) {
			Map cssMap = getLiteralMap(fill.getCssParameterList());
			fontStyle.setColor(cssMap.get(CSS_FILL));
			if (cssMap.containsKey(CSS_FILL_OPACITY)) {
				fontStyle.setOpacity(parseFloat(cssMap, CSS_FILL_OPACITY));
			}
		}
	}

	private FontStyleInfo convertFont(FontInfo font) throws LayerException {
		FontStyleInfo fontStyle = new FontStyleInfo();
		if (font == null) {
			fontStyle.applyDefaults();
		} else {
			Map cssMap = getLiteralMap(font.getCssParameterList());
			fontStyle.setFamily(cssMap.get(CSS_FONT_FAMILY));
			if (cssMap.containsKey(CSS_FONT_SIZE)) {
				fontStyle.setSize((int) parseFloat(cssMap, CSS_FONT_SIZE));
			}
			fontStyle.setStyle(cssMap.get(CSS_FONT_STYLE));
			fontStyle.setWeight(cssMap.get(CSS_FONT_WEIGHT));
		}
		return fontStyle;
	}

	private void convertFill(FeatureStyleInfo featureStyleInfo, FillInfo fill) throws LayerException {
		if (fill != null) {
			Map cssMap = getLiteralMap(fill.getCssParameterList());
			if (cssMap.containsKey(CSS_FILL)) {
				featureStyleInfo.setFillColor(cssMap.get(CSS_FILL));
			}
			if (cssMap.containsKey(CSS_FILL_OPACITY)) {
				featureStyleInfo.setFillOpacity(parseFloat(cssMap, CSS_FILL_OPACITY));
			}
			if (fill.getGraphicFill() != null) {
				GraphicInfo graphic = fill.getGraphicFill().getGraphic();
				for (GraphicInfo.ChoiceInfo choice : graphic.getChoiceList()) {
					if (choice.ifExternalGraphic()) {
						log.debug("Can not display an external graphic fill style for non-rasterized layers.");
						// can't handle this
					} else if (choice.ifMark()) {
						MarkInfo mark = choice.getMark();
						if (mark.getFill() != null) {
							convertFill(featureStyleInfo, mark.getFill());
						}
						if (mark.getStroke() != null) {
							convertStroke(featureStyleInfo, mark.getStroke());
						}
					}
				}
			}
		}
	}

	private void convertStroke(FeatureStyleInfo featureStyleInfo, StrokeInfo stroke) throws LayerException {
		if (stroke != null) {
			Map cssMap = getLiteralMap(stroke.getCssParameterList());
			// not supported are "stroke-linejoin", "stroke-linecap", and "stroke-dashoffset"
			featureStyleInfo.setStrokeColor(cssMap.get(CSS_STROKE));
			if (cssMap.containsKey(CSS_STROKE_OPACITY)) {
				featureStyleInfo.setStrokeOpacity(parseFloat(cssMap, CSS_STROKE_OPACITY));
			}
			if (cssMap.containsKey(CSS_STROKE_WIDTH)) {
				featureStyleInfo.setStrokeWidth((int) parseFloat(cssMap, CSS_STROKE_WIDTH));
			}
			if (cssMap.containsKey(CSS_STROKE_DASH_ARRAY)) {
				featureStyleInfo.setDashArray(cssMap.get(CSS_STROKE_DASH_ARRAY));
			}
		}
	}

	private String convertFormula(FilterTypeInfo filter, FeatureInfo featureInfo) throws LayerException {
		if (filter.ifComparisonOps()) {
			return toComparison(filter.getComparisonOps(), featureInfo);
		} else if (filter.ifFeatureIdList()) {
			return toFeatureIds(filter.getFeatureIdList());
		} else if (filter.ifLogicOps()) {
			return toLogic(filter.getLogicOps(), featureInfo);
		} else if (filter.ifSpatialOps()) {
			return toSpatial(filter.getSpatialOps());
		}
		return null;
	}

	private String toLogic(LogicOpsTypeInfo logicOps, FeatureInfo featureInfo) throws LayerException {
		if (logicOps instanceof UnaryLogicOpTypeInfo) {
			UnaryLogicOpTypeInfo unary = (UnaryLogicOpTypeInfo) logicOps;
			if (unary.ifComparisonOps()) {
				return "NOT " + toComparison(unary.getComparisonOps(), featureInfo);
			} else if (unary.ifLogicOps()) {
				return "NOT " + toLogic(unary.getLogicOps(), featureInfo);
			} else if (unary.ifSpatialOps()) {
				return "NOT " + toSpatial(unary.getSpatialOps());
			}

		} else if (logicOps instanceof BinaryLogicOpTypeInfo) {
			BinaryLogicOpTypeInfo binary = (BinaryLogicOpTypeInfo) logicOps;
			String[] expressions = new String[2];
			for (int i = 0; i < 2; i++) {
				if (binary.getChoiceList().get(i).ifComparisonOps()) {
					expressions[i] = toComparison(binary.getChoiceList().get(i).getComparisonOps(), featureInfo);
				} else if (binary.getChoiceList().get(i).ifLogicOps()) {
					expressions[i] = toLogic(binary.getChoiceList().get(i).getLogicOps(), featureInfo);
				} else if (binary.getChoiceList().get(i).ifSpatialOps()) {
					expressions[i] = toSpatial(binary.getChoiceList().get(i).getSpatialOps());
				}
			}
			if (binary instanceof OrInfo) {
				return "(" + expressions[0] + ") OR (" + expressions[1] + ")";
			} else if (binary instanceof AndInfo) {
				return "(" + expressions[0] + ") AND (" + expressions[1] + ")";
			}
		}
		return null;
	}

	private String toSpatial(SpatialOpsTypeInfo spatialOps) throws LayerException {
		if (spatialOps instanceof BboxTypeInfo) {
			BboxTypeInfo bbox = (BboxTypeInfo) spatialOps;
			String propertyName = bbox.getPropertyName().getValue();
			Envelope envelope = toEnvelope(bbox.getBox());
			return "BBOX (" + propertyName + "," + envelope.getMinX()  + "," + envelope.getMinY()
					+ envelope.getMaxX()  + "," + envelope.getMaxY() + ")";
		} else if (spatialOps instanceof BinarySpatialOpTypeInfo) {
			BinarySpatialOpTypeInfo binary = (BinarySpatialOpTypeInfo) spatialOps;
			String propertyName = binary.getPropertyName().getValue();
			WKTWriter writer = new WKTWriter();
			GeometryFactory factory = new GeometryFactory();
			Geometry geometry = null;
			if (binary.ifGeometry()) {
				AbstractGeometryInfo geom = binary.getGeometry();
				geometry = toGeometry(factory, geom);
			} else if (binary.ifBox()) {
				BoxTypeInfo boxTypeInfo = binary.getBox();
				Envelope envelope = toEnvelope(boxTypeInfo);
				geometry = factory.toGeometry(envelope);
			}
			String wkt = writer.write(geometry);
			if (binary instanceof ContainsInfo) {
				return "CONTAINS(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof CrossesInfo) {
				return "CROSSES(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof DisjointInfo) {
				return "DISJOINT(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof EqualsInfo) {
				return "EQUALS(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof IntersectsInfo) {
				return "INTERSECTS(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof OverlapsInfo) {
				return "OVERLAPS(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof TouchesInfo) {
				return "TOUCHES(" + propertyName + "," + wkt + ")";
			} else if (binary instanceof WithinInfo) {
				return "WITHIN(" + propertyName + "," + wkt + ")";
			} else {
				throw new IllegalArgumentException("Unhandled type of BinarySpatialOpTypeInfo " + binary);
			}
		} else if (spatialOps instanceof DistanceBufferTypeInfo) {
			DistanceBufferTypeInfo distanceBuffer = (DistanceBufferTypeInfo) spatialOps;
			String propertyName = distanceBuffer.getPropertyName().getValue();
			AbstractGeometryInfo geom = distanceBuffer.getGeometry();
			GeometryFactory factory = new GeometryFactory();
			Geometry geometry = toGeometry(factory, geom);
			WKTWriter writer = new WKTWriter();
			String wkt = writer.write(geometry);
			String units = distanceBuffer.getDistance().getUnits();
			String distance = distanceBuffer.getDistance().getValue();
			if (distanceBuffer instanceof DWithinInfo) {
				return "DWITHIN(" + propertyName + "," + wkt + "," + distance + "," + units + ")";
			} else if (distanceBuffer instanceof BeyondInfo) {
				return "BEYOND(" + propertyName + "," + wkt + "," + distance + "," + units + ")";
			} else {
				throw new IllegalArgumentException("Unhandled type of DistanceBufferTypeInfo " + distanceBuffer);
			}
		} else {
			throw new IllegalArgumentException("Unhandled type of SpatialOpsTypeInfo " + spatialOps);
		}
	}
	
	private Envelope toEnvelope(BoxTypeInfo box) throws LayerException {
		if (box.ifCoordinates()) {
			Coordinate[] coords = getCoordinates(box.getCoordinates());
			if (coords.length == 2) {
				return new Envelope(coords[0].x, coords[1].x, coords[0].y, coords[1].y);
			} else {
				throw new IllegalArgumentException("Number of coordinates != 2 in box : " + box);
			}
		} else {
			if (box.getCoordList().size() == 2) {
				CoordTypeInfo coordMin = box.getCoordList().get(0);
				CoordTypeInfo coordMax = box.getCoordList().get(1);
				return new Envelope(coordMin.getX().doubleValue(), coordMax.getX().doubleValue(), coordMin.getY()
						.doubleValue(), coordMax.getY().doubleValue());
			} else {
				throw new IllegalArgumentException("Number of coordinates != 2 in box : " + box);
			}
		}
	}

	private Geometry toGeometry(GeometryFactory factory, AbstractGeometryInfo geom) throws LayerException {
		Geometry geometry = null;
		if (geom instanceof AbstractGeometryCollectionInfo) {
			AbstractGeometryCollectionInfo geomCollection = (AbstractGeometryCollectionInfo) geom;
			List members = geomCollection.getGeometryMemberList();
			if (geom instanceof MultiPointInfo) {
				Point[] points = new Point[members.size()];
				for (int i = 0; i < members.size(); i++) {
					points[i] = (Point) toSimpleGeometry(factory, members.get(i).getGeometry());
				}
				geometry = factory.createMultiPoint(points);
			} else if (geom instanceof MultiLineStringInfo) {
				LineString[] lines = new LineString[members.size()];
				for (int i = 0; i < members.size(); i++) {
					lines[i] = (LineString) toSimpleGeometry(factory, members.get(i).getGeometry());
				}
				geometry = factory.createMultiLineString(lines);
			} else if (geom instanceof MultiPolygonInfo) {
				Polygon[] polygons = new Polygon[members.size()];
				for (int i = 0; i < members.size(); i++) {
					polygons[i] = (Polygon) toSimpleGeometry(factory, members.get(i).getGeometry());
				}
				geometry = factory.createMultiPolygon(polygons);
			} else if (geom instanceof MultiGeometryInfo) {
				Geometry[] geometries = new Geometry[members.size()];
				for (int i = 0; i < members.size(); i++) {
					geometries[i] = toGeometry(factory, members.get(i).getGeometry());
				}
				geometry = factory.createGeometryCollection(geometries);
			}
		} else {
			geometry = toSimpleGeometry(factory, geom);
		}
		return geometry;
	}

	private Geometry toSimpleGeometry(GeometryFactory factory, AbstractGeometryInfo geom) throws LayerException {
		Geometry geometry = null;
		if (geom instanceof PointTypeInfo) {
			PointTypeInfo point = (PointTypeInfo) geom;
			if (point.ifCoord()) {
				geometry = factory.createPoint(getCoordinates(Collections.singletonList(point.getCoord()))[0]);
			} else if (point.ifCoordinates()) {
				geometry = factory.createPoint(getCoordinates(point.getCoordinates())[0]);
			}
		} else if (geom instanceof LineStringTypeInfo) {
			LineStringTypeInfo linestring = (LineStringTypeInfo) geom;
			if (linestring.ifCoordList()) {
				geometry = factory.createLineString(getCoordinates(linestring.getCoordList()));
			} else if (linestring.ifCoordinates()) {
				geometry = factory.createLineString(getCoordinates(linestring.getCoordinates()));
			}
		} else if (geom instanceof PolygonTypeInfo) {
			PolygonTypeInfo polygon = (PolygonTypeInfo) geom;
			OuterBoundaryIsInfo outer = polygon.getOuterBoundaryIs();
			LinearRing shell = toLinearRing(factory, outer.getLinearRing());
			if (polygon.getInnerBoundaryIList() == null) {
				geometry = factory.createPolygon(shell, null);
			} else {
				LinearRing[] holes = new LinearRing[polygon.getInnerBoundaryIList().size()];
				int i = 0;
				for (InnerBoundaryIsInfo inner : polygon.getInnerBoundaryIList()) {
					holes[i++] = toLinearRing(factory, inner.getLinearRing());
				}
				geometry = factory.createPolygon(shell, holes);
			}
		}
		return geometry;
	}

	private LinearRing toLinearRing(GeometryFactory factory, LinearRingTypeInfo linearRing) throws LayerException {
		LinearRing ring = null;
		if (linearRing.ifCoordList()) {
			ring = factory.createLinearRing(getCoordinates(linearRing.getCoordList()));
		} else if (linearRing.ifCoordinates()) {
			ring = factory.createLinearRing(getCoordinates(linearRing.getCoordinates()));
		}
		return ring;
	}

	private Coordinate[] getCoordinates(List coords) {
		Coordinate[] result = new Coordinate[coords.size()];
		int i = 0;
		for (CoordTypeInfo coordinate : coords) {
			result[i++] = new Coordinate(coordinate.getX().doubleValue(), coordinate.getY().doubleValue());
		}
		return result;
	}

	private Coordinate[] getCoordinates(CoordinatesTypeInfo coords) throws LayerException {
		String cs = coords.getCs() == null ? "," : coords.getCs();
		String ts = coords.getTs() == null ? "," : coords.getTs();
		String ds = coords.getDecimal() == null ? "." : coords.getDecimal();
		String[] coordinates = coords.getString().trim().replace(ds, ".").replace(ts, ",")
				.replace(cs, ",").split("[\\s,]+");
		Coordinate[] result = new Coordinate[coordinates.length / 2];
		for (int i = 0; i < coordinates.length; i += 2) {
			double x = parseDouble(coordinates[i].replace(ds, "."));
			double y = parseDouble(coordinates[i + 1].replace(ds, "."));
			result[i / 2] = new Coordinate(x, y);
		}
		return result;
	}

	private String toFeatureIds(List featureIds) {
		StringBuilder stringBuilder = new StringBuilder("IN (");
		for (Iterator it = featureIds.iterator(); it.hasNext();) {
			stringBuilder.append("'").append(it.next().getFid()).append("'");
			stringBuilder.append(it.hasNext() ? "," : ")");
		}
		return stringBuilder.toString();
	}

	private String toPropertyName(String propertyName) {
		return propertyName.replaceAll(ASSOCIATION_DELIMITER_SOURCE, ASSOCIATION_DELIMITER_TARGET);
	}
	
	private String toComparison(ComparisonOpsTypeInfo coOps, FeatureInfo featureInfo) {
		if (coOps instanceof BinaryComparisonOpTypeInfo) {
			BinaryComparisonOpTypeInfo binary = (BinaryComparisonOpTypeInfo) coOps;
			String propertyName = toPropertyName(binary.getExpressionList().get(0).getValue());
			String propertyValue = binary.getExpressionList().get(1).getValue();
			PrimitiveType type = PrimitiveType.STRING;
			for (AbstractAttributeInfo attributeInfo : featureInfo.getAttributes()) {
				if (attributeInfo.getName().equals(propertyName)) {
					if (attributeInfo instanceof PrimitiveAttributeInfo) {
						type = ((PrimitiveAttributeInfo) attributeInfo).getType();
					}
				}
			}
			switch (type) {
				case BOOLEAN:
					propertyValue = propertyValue.toUpperCase();
					break;
				// string types must be quoted
				case DATE:
				case IMGURL:
				case STRING:
				case URL:
				case CURRENCY:
					propertyValue = "'" + propertyValue + "'";
					break;
				// numerical types unquoted
				case DOUBLE:
				case FLOAT:
				case INTEGER:
				case LONG:
				case SHORT:
				default:
					break;
			}
			if (binary instanceof PropertyIsEqualToInfo) {
				return propertyName + " = " + propertyValue;
			} else if (binary instanceof PropertyIsGreaterThanInfo) {
				return propertyName + " > " + propertyValue;
			} else if (binary instanceof PropertyIsGreaterThanOrEqualToInfo) {
				return propertyName + " >= " + propertyValue;
			} else if (binary instanceof PropertyIsLessThanInfo) {
				return propertyName + " < " + propertyValue;
			} else if (binary instanceof PropertyIsLessThanOrEqualToInfo) {
				return propertyName + " <= " + propertyValue;
			} else if (binary instanceof PropertyIsNotEqualToInfo) {
				return propertyName + " != " + propertyValue;
			} else {
				throw new IllegalStateException("Unknown binary comparison operator " + binary);
			}
		} else {
			if (coOps instanceof PropertyIsBetweenTypeInfo) {
				PropertyIsBetweenTypeInfo isBetween = (PropertyIsBetweenTypeInfo) coOps;
				String lower = isBetween.getLowerBoundary().getExpression().getValue();
				String upper = isBetween.getUpperBoundary().getExpression().getValue();
				String propertyName = toPropertyName(isBetween.getExpression().getValue());
				return propertyName + " BETWEEN " + lower + " AND " + upper;
			} else if (coOps instanceof PropertyIsLikeTypeInfo) {
				PropertyIsLikeTypeInfo isLike = (PropertyIsLikeTypeInfo) coOps;
				String propertyName = toPropertyName(isLike.getPropertyName().getValue());
				return propertyName + " LIKE '" + isLike.getLiteral().getValue() + "'";
			} else if (coOps instanceof PropertyIsNullTypeInfo) {
				PropertyIsNullTypeInfo isNull = (PropertyIsNullTypeInfo) coOps;
				String what;
				if (isNull.ifLiteral()) {
					what = isNull.getLiteral().getValue();
				} else {
					what = isNull.getPropertyName().getValue();
				}
				return what + " IS NULL ";
			} else {
				throw new IllegalStateException("Unknown comparison operator " + coOps);
			}
		}
	}

	private Map getLiteralMap(List css) {
		HashMap result = new HashMap();
		if (css != null) {
			for (CssParameterInfo cssParameter : css) {
				// check expressions first, if present ignore value
				if (cssParameter.getExpressionList() != null) {
					if (cssParameter.getExpressionList().size() > 0) {
						ExpressionInfo expression = cssParameter.getExpressionList().get(0);
						if (expression instanceof LiteralTypeInfo) {
							result.put(cssParameter.getName(), expression.getValue());
						}
					}
				} else if (cssParameter.getValue() != null) {
					// ignore spaces and tabs for CSS
					String value = cssParameter.getValue().trim();
					if (!value.isEmpty()) {
						result.put(cssParameter.getName(), value);
					}
				}
			}
		}
		return result;
	}

	private String getParameterValue(ParameterValueTypeInfo parameter) {
		if (parameter.getValue() != null) {
			return parameter.getValue();
		} else if (parameter.getExpressionList().size() > 0) {
			ExpressionInfo expression = parameter.getExpressionList().get(0);
			if (expression instanceof LiteralTypeInfo) {
				return expression.getValue();
			}
		}
		return null;
	}

	private Rule createRule(Filter filter, FeatureStyleInfo featureStyle) throws LayerException {
		Rule rule = styleBuilder.createRule(createGeometrySymbolizer(featureStyle));
		if (filter.equals(Filter.INCLUDE)) {
			rule.setElseFilter(true);
		} else {
			rule.setFilter(filter);
		}
		rule.setName(featureStyle.getName());
		rule.setTitle(featureStyle.getName());
		return rule;
	}

	private Symbolizer createGeometrySymbolizer(FeatureStyleInfo featureStyle) throws LayerException {
		Symbolizer symbolizer;
		switch (featureStyle.getLayerType()) {
			case MULTIPOLYGON:
			case POLYGON:
				symbolizer = styleBuilder.createPolygonSymbolizer(createStroke(featureStyle), createFill(featureStyle));
				break;
			case MULTILINESTRING:
			case LINESTRING:
				symbolizer = styleBuilder.createLineSymbolizer(createStroke(featureStyle));
				break;
			case POINT:
			case MULTIPOINT:
				PointSymbolizer ps = styleBuilder.createPointSymbolizer();
				GraphicalSymbol symbol = createSymbol(featureStyle);
				Expression size = null;
				if (symbol instanceof Mark) {
					SymbolInfo info = featureStyle.getSymbol();
					if (info.getRect() != null) {
						size = styleBuilder.literalExpression((int) info.getRect().getW());
					} else if (info.getCircle() != null) {
						size = styleBuilder.literalExpression(2 * (int) info.getCircle().getR());
					} // else {} already handled by createSymbol()
				} else {
					size = styleBuilder.literalExpression(featureStyle.getSymbol().getImage().getHeight());
				}
				ps.getGraphic().setSize(size);
				ps.getGraphic().graphicalSymbols().clear();
				ps.getGraphic().graphicalSymbols().add(createSymbol(featureStyle));
				symbolizer = ps;
				break;
			default:
				throw new IllegalArgumentException("Unsupported geometry type " + featureStyle.getLayerType());
		}
		return symbolizer;
	}

	private TextSymbolizer createTextSymbolizer(LabelStyleInfo labelStyle, LayerType layerType) {
		Fill fontFill = styleBuilder.createFill(styleBuilder.literalExpression(labelStyle.getFontStyle().getColor()),
				styleBuilder.literalExpression(labelStyle.getFontStyle().getOpacity()));
		TextSymbolizer symbolizer = styleBuilder.createTextSymbolizer();
		symbolizer.setFill(fontFill);
		FontStyleInfo fontInfo = labelStyle.getFontStyle();
		symbolizer.setFont(styleBuilder.createFont(styleBuilder.literalExpression(fontInfo.getFamily()),
				styleBuilder.literalExpression(fontInfo.getStyle()),
				styleBuilder.literalExpression(fontInfo.getWeight()),
				styleBuilder.literalExpression(fontInfo.getSize())));
		symbolizer.setLabel(styleBuilder.attributeExpression(labelStyle.getLabelAttributeName()));
		Fill haloFill = styleBuilder.createFill(
				styleBuilder.literalExpression(labelStyle.getBackgroundStyle().getFillColor()),
				styleBuilder.literalExpression(labelStyle.getBackgroundStyle().getFillOpacity()));
		symbolizer.setHalo(styleBuilder.createHalo(haloFill, 1));
		// label placement : point at bottom-center of label (same as vectorized)
		switch (layerType) {
			case MULTIPOINT:
			case POINT:
				symbolizer.setLabelPlacement(styleBuilder.createPointPlacement(0.5, 0, 0));
				break;
			default:
				break;
		}
		return symbolizer;
	}

	private GraphicalSymbol createSymbol(FeatureStyleInfo featureStyle) throws LayerException {
		SymbolInfo info = featureStyle.getSymbol();
		if (info.getImage() != null) {

			return styleBuilder.createExternalGraphic(getUrl(info.getImage().getHref()), getFormat(info.getImage()
					.getHref()));
		} else {
			Mark mark;
			if (info.getRect() != null) {
				// TODO: do rectangles by adding custom factory ?
				mark = styleBuilder.createMark(MARK_SQUARE);
			} else if (info.getCircle() != null) {
				mark = styleBuilder.createMark(MARK_CIRCLE);
			} else {
				throw new IllegalArgumentException(
						"Feature style should have either an image, a circle or a rectangle defined. Style name: " +
								featureStyle.getName() + ", index: " + featureStyle.getIndex());
			}
			mark.setFill(createFill(featureStyle));
			mark.setStroke(createStroke(featureStyle));
			return mark;
		}
	}

	private Stroke createStroke(FeatureStyleInfo featureStyle) throws LayerException {
		Stroke stroke = styleBuilder.createStroke(styleBuilder.literalExpression(featureStyle.getStrokeColor()),
				styleBuilder.literalExpression(featureStyle.getStrokeWidth()),
				styleBuilder.literalExpression(featureStyle.getStrokeOpacity()));
		if (featureStyle.getDashArray() != null) {
			String[] strings = featureStyle.getDashArray().split(",");
			float[] nrs = new float[strings.length];
			for (int i = 0; i < strings.length; i++) {
				try {
					nrs[i] = parseFloat(strings[i]);
				} catch (NumberFormatException e) {
					log.warn("Dash array cannot be parsed " + featureStyle.getDashArray(), e);
				}
			}
			stroke.setDashArray(nrs);
		}
		return stroke;
	}

	private Fill createFill(FeatureStyleInfo featureStyle) {
		return styleBuilder.createFill(styleBuilder.literalExpression(featureStyle.getFillColor()),
				styleBuilder.literalExpression(featureStyle.getFillOpacity()));
	}

	private URL getUrl(String resourceLocation) throws LayerException {
		if (resourceLocation.startsWith(GeomajasConstant.CLASSPATH_URL_PREFIX)) {
			resourceLocation = resourceLocation.substring(GeomajasConstant.CLASSPATH_URL_PREFIX.length());
		}
		Resource resource = applicationContext.getResource(resourceLocation);
		try {
			if (resource.exists()) {
				return resource.getURL();
			} else {
				String gwtResource = GeomajasConstant.CLASSPATH_URL_PREFIX + resourceLocation;
				Resource[] matching = applicationContext.getResources(gwtResource);
				if (matching.length > 0) {
					return matching[0].getURL();
				} else {
					log.warn(MISSING_RESOURCE, gwtResource);
					throw new LayerException(ExceptionCode.RESOURCE_NOT_FOUND, gwtResource);
				}
			}
		} catch (IOException e) {
			log.warn(MISSING_RESOURCE, resourceLocation);
			throw new LayerException(e, ExceptionCode.RESOURCE_NOT_FOUND, resourceLocation);
		}
	}

	private String getFormat(String href) {
		return "image/" + StringUtils.getFilenameExtension(href);
	}

	/**
	 * Finish service initialization.
	 */
	@PostConstruct
	protected void postConstruct() {
		styleBuilder = new StyleBuilder(filterService.getFilterFactory());
	}
	
	/**
	 * A custom {@link ResourceLocator} that uses the {@link ResourceService} for URL location.
	 * 
	 * @author Jan De Moerloose
	 * 
	 */
	class ResourceServiceBasedLocator implements ResourceLocator {

		public URL locateResource(String uri) {
			URL url = null;
			try {
				Resource resource = resourceService.find(uri);
				url = resource.getURL();
			} catch (Exception e) { // NOSONAR
				log.warn(MISSING_RESOURCE, uri);
			}
			return url;
		}

	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy