/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 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.gwt2.plugin.wfs.server.command.factory.impl; import java.util.ArrayList; import java.util.List; import org.geomajas.geometry.Bbox; import org.geomajas.global.GeomajasException; import org.geomajas.gwt2.client.map.attribute.AttributeDescriptor; import org.geomajas.gwt2.client.map.attribute.GeometryAttributeType; import org.geomajas.gwt2.client.map.feature.query.AttributeCriterion; import org.geomajas.gwt2.client.map.feature.query.BboxCriterion; import org.geomajas.gwt2.client.map.feature.query.Criterion; import org.geomajas.gwt2.client.map.feature.query.CriterionVisitor; import org.geomajas.gwt2.client.map.feature.query.DWithinCriterion; import org.geomajas.gwt2.client.map.feature.query.ExcludeCriterion; import org.geomajas.gwt2.client.map.feature.query.FidCriterion; import org.geomajas.gwt2.client.map.feature.query.FullTextCriterion; import org.geomajas.gwt2.client.map.feature.query.GeometryCriterion; import org.geomajas.gwt2.client.map.feature.query.IncludeCriterion; import org.geomajas.gwt2.client.map.feature.query.LogicalCriterion; import org.geomajas.gwt2.plugin.wfs.server.command.factory.CriterionToFilterConverter; import org.geomajas.service.DtoConverterService; import org.geomajas.service.FilterService; import org.geotools.filter.text.ecql.ECQL; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.Literal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateFilter; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier; /** * Converts {@link Criterion} to a geotools {@link Filter}. * * @author Jan De Moerloose * */ public class DefaultCriterionFilterConverter implements CriterionToFilterConverter, CriterionVisitor { private final Logger log = LoggerFactory.getLogger(DefaultCriterionFilterConverter.class); private FilterService filterService; private DtoConverterService converterService; private int maxCoordinatesInGeometry = -1; private boolean roundCoordinates; private int roundNumberOfDecimals = 2; public DefaultCriterionFilterConverter(FilterService filterService, DtoConverterService converterService) { this.filterService = filterService; this.converterService = converterService; } public void setMaxCoordinatesInGeometry(int maxCoordinatesInGeometry) { this.maxCoordinatesInGeometry = maxCoordinatesInGeometry; } public void setRoundCoordinates(boolean roundCoordinates) { this.roundCoordinates = roundCoordinates; } public void setRoundNumberOfDecimals(int roundNumberOfDecimals) { this.roundNumberOfDecimals = roundNumberOfDecimals; } /** * Convert a criterion to a filter. * * @param criterionDto * @param schema * @return */ public Filter convert(Criterion criterionDto, List<AttributeDescriptor> schema) { FilterContext fc = new FilterContext(); fc.setSchema(schema); criterionDto.accept(this, fc); try { log.debug("Filter converted : " + ECQL.toCQL(fc.getFilter())); } catch (Exception e) { // ignore } return fc.getFilter(); } @Override public void visit(LogicalCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; if (criterion.getChildren().size() == 0) { fc.setFilter(Filter.EXCLUDE); } else { Filter filter = null; for (Criterion child : criterion.getChildren()) { child.accept(this, fc); if (filter == null) { filter = fc.getFilter(); } else { filter = filterService.createLogicFilter(filter, criterion.getOperator().name(), fc.getFilter()); } } fc.setFilter(filter); } } @Override public void visit(AttributeCriterion<?> criterion, Object context) { FilterContext fc = (FilterContext) context; String operation = criterion.getOperation(); String name = criterion.getAttributeName(); Filter filter = null; if ("like".equalsIgnoreCase(operation)) { filter = filterService.createLikeFilter(name, criterion.getValue().toString()); } else if ("!=".equalsIgnoreCase(operation)) { filter = filterService.createCompareFilter(name, "<>", criterion.getValue().toString()); } else { filter = filterService.createCompareFilter(name, operation, criterion.getValue().toString()); } fc.setFilter(filter); } @Override public void visit(BboxCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; Bbox bbox = criterion.getBbox(); Envelope envelope = converterService.toInternal(bbox); String name = criterion.getAttributeName(); if (name == null) { name = fc.getDefaultGeometryName(); } String crs = criterion.getCrs(); Filter filter = filterService.createBboxFilter(crs, envelope, name); fc.setFilter(filter); } @Override public void visit(FidCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; String[] fids = criterion.getFids(); Filter filter = filterService.createFidFilter(fids); fc.setFilter(filter); } @Override public void visit(GeometryCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; String operation = criterion.getOperation(); String name = criterion.getAttributeName(); if (name == null) { name = fc.getDefaultGeometryName(); } Geometry geometry; try { geometry = converterService.toInternal(criterion.getValue()); geometry = check(geometry); Filter filter = null; FilterFactory2 ff = (FilterFactory2) filterService.getFilterFactory(); Expression nameExpression = ff.property(name); Literal geomLiteral = ff.literal(geometry); if (GeometryCriterion.CONTAINS.equalsIgnoreCase(operation)) { filter = filterService.createContainsFilter(geometry, name); } else if (GeometryCriterion.CROSSES.equalsIgnoreCase(operation)) { filter = ff.crosses(nameExpression, geomLiteral); } else if (GeometryCriterion.DISJOINT.equalsIgnoreCase(operation)) { filter = ff.disjoint(nameExpression, geomLiteral); } else if (GeometryCriterion.EQUALS.equalsIgnoreCase(operation)) { filter = ff.equals(nameExpression, geomLiteral); } else if (GeometryCriterion.INTERSECTS.equalsIgnoreCase(operation)) { filter = filterService.createIntersectsFilter(geometry, name); } else if (GeometryCriterion.OVERLAPS.equalsIgnoreCase(operation)) { filter = filterService.createOverlapsFilter(geometry, name); } else if (GeometryCriterion.TOUCHES.equalsIgnoreCase(operation)) { filter = filterService.createTouchesFilter(geometry, name); } else if (GeometryCriterion.WITHIN.equalsIgnoreCase(operation)) { filter = filterService.createWithinFilter(geometry, name); } fc.setFilter(filter); } catch (GeomajasException e) { throw new IllegalArgumentException("Unparseable geometry in filter"); } } @Override public void visit(DWithinCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; String name = criterion.getAttributeName(); if (name == null) { name = fc.getDefaultGeometryName(); } Geometry geometry; try { geometry = converterService.toInternal(criterion.getValue()); geometry = check(geometry); FilterFactory2 ff = (FilterFactory2) filterService.getFilterFactory(); Expression nameExpression = ff.property(name); Literal geomLiteral = ff.literal(geometry); Filter filter = ff.dwithin(nameExpression, geomLiteral, criterion.getDistance(), criterion.getUnits()); fc.setFilter(filter); } catch (GeomajasException e) { throw new IllegalArgumentException("Unparseable geometry in filter"); } } private Geometry check(Geometry geometry) { if (maxCoordinatesInGeometry > 0) { int distanceTolerance = 10; while (geometry.getNumPoints() > maxCoordinatesInGeometry) { geometry = DouglasPeuckerSimplifier.simplify(geometry, distanceTolerance); distanceTolerance *= 2; } } if (roundCoordinates) { final double factor = Math.pow(10, roundNumberOfDecimals); geometry.apply(new CoordinateFilter() { @Override public void filter(Coordinate coord) { coord.x = Math.round(coord.x * factor) / factor; coord.y = Math.round(coord.y * factor) / factor; } }); } return geometry; } @Override public void visit(FullTextCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; List<AttributeDescriptor> schema = fc.getSchema(); String key = "*" + criterion.getKey() + "*"; if (schema == null || schema.size() == 0) { fc.setFilter(Filter.EXCLUDE); } else { List<Filter> filters = new ArrayList<Filter>(); for (AttributeDescriptor descriptor : schema) { if (descriptor.getType().getBinding().equals(String.class)) { filters.add(filterService.createLikeFilter(descriptor.getName(), key)); } } if (filters.size() > 1) { FilterFactory2 ff = (FilterFactory2) filterService.getFilterFactory(); fc.setFilter(ff.or(filters)); } else if (filters.size() == 1) { fc.setFilter(filters.get(0)); } else { fc.setFilter(Filter.EXCLUDE); } } } @Override public void visit(IncludeCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; fc.setFilter(Filter.INCLUDE); } @Override public void visit(ExcludeCriterion criterion, Object context) { FilterContext fc = (FilterContext) context; fc.setFilter(Filter.EXCLUDE); } @Override public void visit(Criterion criterion, Object context) { } /** * Context class to pass along the filter. * * @author Jan De Moerloose * */ class FilterContext { private Filter filter; private List<AttributeDescriptor> schema; public Filter getFilter() { return filter; } public List<AttributeDescriptor> getSchema() { return schema; } public void setSchema(List<AttributeDescriptor> schema) { this.schema = schema; } public void setFilter(Filter filter) { this.filter = filter; } public String getDefaultGeometryName() { if (schema != null) { for (AttributeDescriptor attributeDescriptor : schema) { if (attributeDescriptor.getType() instanceof GeometryAttributeType) { return attributeDescriptor.getName(); } } } return "the_geom"; } } }