sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jso...@apache.org
Subject [sis] branch geoapi-4.0 updated: Filter : implement and tests for spatial functions
Date Wed, 06 Nov 2019 15:30:14 GMT
This is an automated email from the ASF dual-hosted git repository.

jsorel pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 17aabb3  Filter : implement and tests for spatial functions
17aabb3 is described below

commit 17aabb3c845038a9186adba7e98639cafecc3424
Author: jsorel <johann.sorel@geomatys.com>
AuthorDate: Wed Nov 6 16:30:00 2019 +0100

    Filter : implement and tests for spatial functions
---
 .../apache/sis/filter/DefaultFilterFactory.java    |   2 -
 .../org/apache/sis/filter/SpatialFunction.java     | 521 ++++++++++++++++++++-
 .../org/apache/sis/filter/SpatialFunctionTest.java | 271 +++++++++++
 .../apache/sis/test/suite/FeatureTestSuite.java    |   1 +
 4 files changed, 782 insertions(+), 13 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
index 4d5002d..8d27c54 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
@@ -22,8 +22,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.opengis.filter.*;
 import org.opengis.filter.capability.*;
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/SpatialFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/SpatialFunction.java
index 6d40969..2e770c9 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/SpatialFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/SpatialFunction.java
@@ -18,14 +18,41 @@ package org.apache.sis.filter;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.measure.Unit;
+import javax.measure.UnitConverter;
+import org.apache.sis.coverage.grid.GridCoverage;
+import org.apache.sis.internal.feature.AttributeConvention;
+import org.apache.sis.internal.feature.Geometries;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.measure.Units;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
+import org.opengis.feature.Feature;
+import org.opengis.feature.PropertyNotFoundException;
 import org.opengis.filter.FilterVisitor;
 import org.opengis.filter.expression.Expression;
 import org.opengis.filter.expression.PropertyName;
 import org.opengis.filter.spatial.BinarySpatialOperator;
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.MismatchedDimensionException;
+import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.TransformException;
 import org.opengis.util.FactoryException;
 
 /**
@@ -37,6 +64,19 @@ import org.opengis.util.FactoryException;
  */
 abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOperator {
 
+    private static final LinearRing[] EMPTY_RINGS = new LinearRing[0];
+    private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
+    private static final PreparedGeometryFactory PREPARED_FACTORY = new PreparedGeometryFactory();
+
+    private static CoordinateReferenceSystem MERCATOR;
+
+    private static CoordinateReferenceSystem getMercator() throws FactoryException {
+        if (MERCATOR == null) {
+            MERCATOR = CRS.forCode("EPSG:3395");
+        }
+        return MERCATOR;
+    }
+
     protected SpatialFunction(Expression expression1, Expression expression2) {
         super(expression1, expression2);
     }
@@ -66,17 +106,214 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
         throw new UnsupportedOperationException("Not supported.");
     }
 
+    private static Geometry toGeometry(final Object object, Expression exp) {
+        Object value = null;
+        if ((exp instanceof PropertyName) && object instanceof Feature &&
((PropertyName) exp).getPropertyName().isEmpty()) {
+            //Search for a default geometry.
+            try {
+                value = ((Feature) object).getPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString());
+            } catch (PropertyNotFoundException ex) {
+                //no defined default geometry
+            }
+        } else {
+            value = exp.evaluate(object);
+        }
+
+        Geometry candidate;
+        if (value instanceof GridCoverage) {
+            candidate = (Geometry) value;
+        } else if (value instanceof GridCoverage) {
+            //use the coverage envelope
+            final GridCoverage coverage = (GridCoverage) value;
+            candidate = toGeometry(coverage.getGridGeometry().getEnvelope());
+        } else {
+            try {
+                candidate = ObjectConverters.convert(value, Geometry.class);
+            } catch (UnconvertibleObjectException ex) {
+                //cound not convert value to a Geometry
+                candidate = null;
+            }
+        }
+        return candidate;
+    }
+
+    /**
+     * Envelope to geometry.
+     */
+    private static Polygon toGeometry(final Envelope env) {
+        final Coordinate[] coordinates = new Coordinate[]{
+            new Coordinate(env.getMinimum(0), env.getMinimum(1)),
+            new Coordinate(env.getMinimum(0), env.getMaximum(1)),
+            new Coordinate(env.getMaximum(0), env.getMaximum(1)),
+            new Coordinate(env.getMaximum(0), env.getMinimum(1)),
+            new Coordinate(env.getMinimum(0), env.getMinimum(1))};
+        final LinearRing ring = GEOMETRY_FACTORY.createLinearRing(coordinates);
+        Polygon polygon = GEOMETRY_FACTORY.createPolygon(ring, new LinearRing[0]);
+        polygon.setUserData(env.getCoordinateReferenceSystem());
+        return polygon;
+    }
+
+    /**
+     * Envelope to prepared geometry.
+     */
+    private static PreparedGeometry toPreparedGeometry(final Envelope env){
+        double minX = env.getMinimum(0);
+        double minY = env.getMinimum(1);
+        double maxX = env.getMaximum(0);
+        double maxY = env.getMaximum(1);
+        if (Double.isNaN(minX) || Double.isInfinite(minX)) minX = Double.MIN_VALUE;
+        if (Double.isNaN(minY) || Double.isInfinite(minY)) minY = Double.MIN_VALUE;
+        if (Double.isNaN(maxX) || Double.isInfinite(maxX)) maxX = Double.MAX_VALUE;
+        if (Double.isNaN(maxY) || Double.isInfinite(maxY)) maxY = Double.MAX_VALUE;
+
+        final Coordinate[] coords = new Coordinate[5];
+        coords[0] = new Coordinate(minX, minY);
+        coords[1] = new Coordinate(minX, maxY);
+        coords[2] = new Coordinate(maxX, maxY);
+        coords[3] = new Coordinate(maxX, minY);
+        coords[4] = new Coordinate(minX, minY);
+        final LinearRing ring = GEOMETRY_FACTORY.createLinearRing(coords);
+        Geometry geom = GEOMETRY_FACTORY.createPolygon(ring, EMPTY_RINGS);
+        return PREPARED_FACTORY.create(geom);
+    }
+
+    /**
+     * Reproject geometries to the same CRS if needed and if possible.
+     */
+    private static Geometry[] toSameCRS(final Geometry leftGeom, final Geometry rightGeom)
+            throws NoSuchAuthorityCodeException, FactoryException, TransformException {
+
+        final CoordinateReferenceSystem leftCRS = Geometries.getCoordinateReferenceSystem(leftGeom);
+        final CoordinateReferenceSystem rightCRS = Geometries.getCoordinateReferenceSystem(rightGeom);
+
+        if (leftCRS == null || rightCRS == null) {
+            //one or both geometries doesn't have a defined CRS, we assume that both
+            //are in the same CRS
+            return new Geometry[]{leftGeom, rightGeom};
+        } else if (Utilities.equalsIgnoreMetadata(leftCRS, rightCRS)) {
+            //both are in the same CRS, nothing to reproject
+            return new Geometry[]{leftGeom, rightGeom};
+        }
+
+        //we choose to reproject the right operand.
+        //there is no special reason to make this choice but we must make one.
+        //perhaps there could be a way to determine a best crs ?
+        final CoordinateOperation trs = CRS.findOperation(rightCRS, leftCRS, null);
+
+        return new Geometry[]{leftGeom, (Geometry) Geometries.transform(rightGeom, trs)};
+    }
+
+    /**
+     * Reproject one or both geometries to the same crs, the matching crs will
+     * be compatible with the requested unit. return Array[leftGeometry,
+     * rightGeometry, matchingCRS];
+     */
+    private static Object[] toSameCRS(final Geometry leftGeom, final Geometry rightGeom,
final Unit unit)
+            throws NoSuchAuthorityCodeException, FactoryException, TransformException {
+
+        final CoordinateReferenceSystem leftCRS = Geometries.getCoordinateReferenceSystem(leftGeom);
+        final CoordinateReferenceSystem rightCRS = Geometries.getCoordinateReferenceSystem(rightGeom);
+
+        if (leftCRS == null && rightCRS == null) {
+            //bother geometries doesn't have a defined SRID, we assume that both
+            //are in the same CRS
+            return new Object[]{leftGeom, rightGeom, null};
+        } else if (leftCRS == null || rightCRS == null || Utilities.equalsIgnoreMetadata(leftCRS,
rightCRS)) {
+            //both are in the same CRS
+
+            final CoordinateReferenceSystem geomCRS = (leftCRS == null) ? rightCRS : leftCRS;
+
+            if (geomCRS.getCoordinateSystem().getAxis(0).getUnit().isCompatible(unit)) {
+                //the geometries crs is compatible with the requested unit, nothing to reproject
+                return new Object[]{leftGeom, rightGeom, geomCRS};
+            } else {
+                //the crs unit is not compatible, we must reproject both geometries to a
more appropriate crs
+                if (Units.METRE.isCompatible(unit)) {
+                    //in that case we reproject to mercator EPSG:3395
+                    final CoordinateReferenceSystem mercator = getMercator();
+                    final CoordinateOperation trs = CRS.findOperation(geomCRS, mercator,
null);
+
+                    return new Object[]{
+                        Geometries.transform(leftGeom, trs),
+                        Geometries.transform(rightGeom, trs),
+                        mercator};
+
+                } else {
+                    //we can not find a matching projection in this case
+                    throw new TransformException("Could not find a matching CRS for both
geometries for unit :" + unit);
+                }
+            }
+
+        } else {
+            //both have different CRS, try to find the most appropriate crs amoung both
+
+            final CoordinateReferenceSystem matchingCRS;
+            final Object leftMatch;
+            final Object rightMatch;
+
+            if (leftCRS.getCoordinateSystem().getAxis(0).getUnit().isCompatible(unit)) {
+                matchingCRS = leftCRS;
+                final CoordinateOperation trs = CRS.findOperation(rightCRS, matchingCRS,
null);
+                rightMatch = Geometries.transform(rightGeom, trs);
+                leftMatch = leftGeom;
+            } else if (rightCRS.getCoordinateSystem().getAxis(0).getUnit().isCompatible(unit))
{
+                matchingCRS = rightCRS;
+                final CoordinateOperation trs = CRS.findOperation(leftCRS, matchingCRS, null);
+                leftMatch = Geometries.transform(leftGeom, trs);
+                rightMatch = rightGeom;
+            } else {
+                //the crs unit is not compatible, we must reproject both geometries to a
more appropriate crs
+                if (Units.METRE.isCompatible(unit)) {
+                    //in that case we reproject to mercator EPSG:3395
+                    matchingCRS = getMercator();
+
+                    CoordinateOperation trs = CRS.findOperation(leftCRS, matchingCRS, null);
+                    leftMatch = Geometries.transform(leftGeom, trs);
+                    trs = CRS.findOperation(rightCRS, matchingCRS, null);
+                    rightMatch = Geometries.transform(rightGeom, trs);
+
+                } else {
+                    //we can not find a matching projection in this case
+                    throw new TransformException("Could not find a matching CRS for both
geometries for unit :" + unit);
+                }
+            }
+
+            return new Object[]{leftMatch, rightMatch, matchingCRS};
+        }
+    }
+
 
     /**
      * The {@value #NAME} filter.
      */
     static final class BBOX extends SpatialFunction implements org.opengis.filter.spatial.BBOX
{
 
+        //cache the bbox geometry
+        private transient PreparedGeometry boundingGeometry;
+        private final org.locationtech.jts.geom.Envelope boundingEnv;
+        private final CoordinateReferenceSystem crs;
+
         private final Envelope env;
 
         BBOX(Expression expression, Envelope env) {
             super(expression, new LeafExpression.Literal(env));
             this.env = env;
+            boundingGeometry = toPreparedGeometry(env);
+            boundingEnv = boundingGeometry.getGeometry().getEnvelopeInternal();
+            final CoordinateReferenceSystem crsFilter = env.getCoordinateReferenceSystem();
+            if (crsFilter != null) {
+                this.crs = crsFilter;
+            } else {
+                // In CQL if crs is not specified, it is EPSG:4326
+                this.crs = CommonCRS.WGS84.normalizedGeographic();
+            }
+        }
+
+        private PreparedGeometry getPreparedGeometry(){
+            if (boundingGeometry == null) {
+                boundingGeometry = toPreparedGeometry(env);
+            }
+            return boundingGeometry;
         }
 
         @Override
@@ -123,7 +360,42 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry candidate = toGeometry(object, expression1);
+
+            if (candidate == null) {
+                return false;
+            }
+
+            //we don't know in which crs it is, try to find it
+            CoordinateReferenceSystem candidateCrs = null;
+            try {
+                candidateCrs = Geometries.getCoordinateReferenceSystem(candidate);
+            } catch (FactoryException ex) {
+                warning(ex);
+            }
+
+            //if we don't know the crs, we will assume it's the objective crs already
+            if (candidateCrs != null) {
+                //reproject in objective crs if needed
+                if (!Utilities.equalsIgnoreMetadata(this.crs, candidateCrs)) {
+                    try {
+                        candidate = (Geometry) Geometries.transform(candidate, CRS.findOperation(candidateCrs,
this.crs, null));
+                    } catch (MismatchedDimensionException | TransformException | FactoryException
ex) {
+                        warning(ex);
+                        return false;
+                    }
+                }
+            }
+
+            final org.locationtech.jts.geom.Envelope candidateEnv = candidate.getEnvelopeInternal();
+
+            if (boundingEnv.contains(candidateEnv) || candidateEnv.contains(boundingEnv))
{
+                return true;
+            } else if (boundingEnv.intersects(candidateEnv)) {
+                return getPreparedGeometry().intersects(candidate);
+            } else {
+                return false;
+            }
         }
 
         @Override
@@ -139,11 +411,13 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         private final double distance;
         private final String units;
+        private final Unit unit;
 
         Beyond(Expression expression1, Expression expression2, double distance, String units)
{
             super(expression1, expression2);
             this.distance = distance;
             this.units = units;
+            this.unit = Units.valueOf(units);
         }
 
         @Override
@@ -163,7 +437,32 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            final Geometry leftGeom = toGeometry(object, expression1);
+            final Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            try {
+                final Object[] values = toSameCRS(leftGeom, rightGeom, unit);
+
+                if (values[2] == null) {
+                    //no matching crs was found, assume both have the same and valid unit
+                    return !leftGeom.isWithinDistance(rightGeom, distance);
+                } else {
+                    final Geometry leftMatch = (Geometry) values[0];
+                    final Geometry rightMatch = (Geometry) values[1];
+                    final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) values[2];
+                    final UnitConverter converter = unit.getConverterTo(crs.getCoordinateSystem().getAxis(0).getUnit());
+
+                    return !leftMatch.isWithinDistance(rightMatch, converter.convert(distance));
+                }
+
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
         }
 
         @Override
@@ -188,7 +487,30 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envLeft.contains(envRight)) {
+                return leftGeom.contains(rightGeom);
+            }
+            return false;
         }
 
         @Override
@@ -213,7 +535,31 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envRight.intersects(envLeft)) {
+                return leftGeom.crosses(rightGeom);
+            }
+
+            return false;
         }
 
         @Override
@@ -238,7 +584,31 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envRight.intersects(envLeft)) {
+                return leftGeom.disjoint(rightGeom);
+            }
+
+            return true;
         }
 
         @Override
@@ -254,11 +624,13 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         private final double distance;
         private final String units;
+        private final Unit unit;
 
         DWithin(Expression expression1, Expression expression2, double distance, String units)
{
             super(expression1, expression2);
             this.distance = distance;
             this.units = units;
+            this.unit = Units.valueOf(units);
         }
 
         @Override
@@ -278,7 +650,32 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            final Geometry leftGeom = toGeometry(object, expression1);
+            final Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            try {
+                final Object[] values = toSameCRS(leftGeom, rightGeom, unit);
+
+                if (values[2] == null) {
+                    //no matching crs was found, assume both have the same and valid unit
+                    return leftGeom.isWithinDistance(rightGeom, distance);
+                } else {
+                    final Geometry leftMatch = (Geometry) values[0];
+                    final Geometry rightMatch = (Geometry) values[1];
+                    final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) values[2];
+                    final UnitConverter converter = unit.getConverterTo(crs.getCoordinateSystem().getAxis(0).getUnit());
+
+                    return leftMatch.isWithinDistance(rightMatch, converter.convert(distance));
+                }
+
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
         }
 
         @Override
@@ -303,7 +700,24 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            return leftGeom.equals(rightGeom);
         }
 
         @Override
@@ -328,7 +742,30 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envLeft.intersects(envRight)) {
+                return leftGeom.intersects(rightGeom);
+            }
+            return false;
         }
 
         @Override
@@ -353,7 +790,30 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envLeft.intersects(envRight)) {
+                return leftGeom.overlaps(rightGeom);
+            }
+            return false;
         }
 
         @Override
@@ -378,7 +838,23 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+            return leftGeom.touches(rightGeom);
         }
 
         @Override
@@ -403,7 +879,30 @@ abstract class SpatialFunction extends BinaryFunction implements BinarySpatialOp
 
         @Override
         public boolean evaluate(Object object) {
-            throw new UnsupportedOperationException("Not supported yet.");
+            Geometry leftGeom = toGeometry(object, expression1);
+            Geometry rightGeom = toGeometry(object, expression2);
+
+            if (leftGeom == null || rightGeom == null) {
+                return false;
+            }
+
+            final Geometry[] values;
+            try {
+                values = toSameCRS(leftGeom, rightGeom);
+            } catch (FactoryException | TransformException ex) {
+                warning(ex);
+                return false;
+            }
+            leftGeom = values[0];
+            rightGeom = values[1];
+
+            final org.locationtech.jts.geom.Envelope envLeft = leftGeom.getEnvelopeInternal();
+            final org.locationtech.jts.geom.Envelope envRight = rightGeom.getEnvelopeInternal();
+
+            if (envRight.contains(envLeft)) {
+                return leftGeom.within(rightGeom);
+            }
+            return false;
         }
 
         @Override
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/SpatialFunctionTest.java
b/core/sis-feature/src/test/java/org/apache/sis/filter/SpatialFunctionTest.java
new file mode 100644
index 0000000..fbf20e0
--- /dev/null
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/SpatialFunctionTest.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.filter;
+
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LinearRing;
+import org.opengis.filter.FilterFactory2;
+import org.opengis.filter.spatial.BBOX;
+import org.opengis.filter.spatial.Beyond;
+import org.opengis.filter.spatial.Contains;
+import org.opengis.filter.spatial.Crosses;
+import org.opengis.filter.spatial.DWithin;
+import org.opengis.filter.spatial.Disjoint;
+import org.opengis.filter.spatial.Equals;
+import org.opengis.filter.spatial.Intersects;
+import org.opengis.filter.spatial.Overlaps;
+import org.opengis.filter.spatial.Touches;
+import org.opengis.filter.spatial.Within;
+
+
+/**
+ * Tests {@link SpatialFunction} implementations.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @version 2.0
+ * @since   2.0
+ * @module
+ */
+public final strictfp class SpatialFunctionTest extends TestCase {
+    /**
+     * The factory to use for creating the objects to test.
+     */
+    private final FilterFactory2 FF;
+    private final GeometryFactory GF;
+
+    /**
+     * Expressions used as constant for the tests.
+     */
+    private final Geometry GEOM_DISTANCE_1;
+    private final Geometry GEOM_DISTANCE_3;
+    private final Geometry GEOM_INTERSECT;
+    private final Geometry GEOM_CONTAINS;
+    private final Geometry GEOM_CROSSES;
+    private final Geometry GEOM_TOUCHES;
+    private final Geometry RIGHT_GEOMETRY;
+
+    public SpatialFunctionTest() {
+        FF = new DefaultFilterFactory();
+        GF = new GeometryFactory();
+
+        Coordinate[] coords = new Coordinate[5];
+        coords[0] = new Coordinate(5, 1);
+        coords[1] = new Coordinate(10, 1);
+        coords[2] = new Coordinate(10, 4);
+        coords[3] = new Coordinate(5, 4);
+        coords[4] = new Coordinate(5, 1);
+        LinearRing ring = GF.createLinearRing(coords);
+        GEOM_DISTANCE_1 = GF.createPolygon(ring, new LinearRing[0]);
+
+        coords = new Coordinate[5];
+        coords[0] = new Coordinate(5, -1);
+        coords[1] = new Coordinate(10, -1);
+        coords[2] = new Coordinate(10, 2);
+        coords[3] = new Coordinate(5, 2);
+        coords[4] = new Coordinate(5, -1);
+        ring = GF.createLinearRing(coords);
+        GEOM_DISTANCE_3 = GF.createPolygon(ring, new LinearRing[0]);
+
+        coords = new Coordinate[5];
+        coords[0] = new Coordinate(7, 3);
+        coords[1] = new Coordinate(9, 3);
+        coords[2] = new Coordinate(9, 6);
+        coords[3] = new Coordinate(7, 6);
+        coords[4] = new Coordinate(7, 3);
+        ring = GF.createLinearRing(coords);
+        GEOM_INTERSECT = GF.createPolygon(ring, new LinearRing[0]);
+
+        coords = new Coordinate[5];
+        coords[0] = new Coordinate(1, 1);
+        coords[1] = new Coordinate(11, 1);
+        coords[2] = new Coordinate(11, 20);
+        coords[3] = new Coordinate(1, 20);
+        coords[4] = new Coordinate(1, 1);
+        ring = GF.createLinearRing(coords);
+        GEOM_CONTAINS = GF.createPolygon(ring, new LinearRing[0]);
+
+        coords = new Coordinate[3];
+        coords[0] = new Coordinate(4, 6);
+        coords[1] = new Coordinate(7, 8);
+        coords[2] = new Coordinate(12, 9);
+        GEOM_CROSSES = GF.createLineString(coords);
+
+        coords = new Coordinate[3];
+        coords[0] = new Coordinate(4, 2);
+        coords[1] = new Coordinate(7, 5);
+        coords[2] = new Coordinate(9, 3);
+        GEOM_TOUCHES = GF.createLineString(coords);
+
+
+        coords = new Coordinate[5];
+        coords[0] = new Coordinate(5, 5);
+        coords[1] = new Coordinate(5, 10);
+        coords[2] = new Coordinate(10,10);
+        coords[3] = new Coordinate(10,5);
+        coords[4] = new Coordinate(5,5);
+        ring = GF.createLinearRing(coords);
+        RIGHT_GEOMETRY = GF.createPolygon(ring, new LinearRing[0]);
+    }
+
+    @Test
+    public void testBBOX() {
+        BBOX bbox = FF.bbox(FF.literal(RIGHT_GEOMETRY), 1, 1, 6, 6, "EPSG:4326");
+        assertTrue(bbox.evaluate(null));
+
+        bbox = FF.bbox(FF.literal(RIGHT_GEOMETRY), -3, -2, 4, 1, "EPSG:4326");
+        assertFalse(bbox.evaluate(null));
+    }
+
+    @Test
+    public void testBeyond() {
+        //we can not test units while using jts geometries
+
+        Beyond beyond = FF.beyond(FF.literal(RIGHT_GEOMETRY), FF.literal(GEOM_DISTANCE_1),
1.5d, "m");
+        assertFalse(beyond.evaluate(null));
+
+        beyond = FF.beyond(FF.literal(RIGHT_GEOMETRY), FF.literal(GEOM_DISTANCE_3), 1.5d,
"m");
+        assertTrue(beyond.evaluate(null));
+    }
+
+    @Test
+    public void testContains() {
+        Contains contains = FF.contains(FF.literal(GEOM_CONTAINS),FF.literal(RIGHT_GEOMETRY));
+        assertTrue(contains.evaluate(null));
+
+        contains = FF.contains(FF.literal(GEOM_DISTANCE_1),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(contains.evaluate(null));
+    }
+
+    @Test
+    public void testCrosses() {
+        Crosses crosses = FF.crosses(FF.literal(GEOM_CONTAINS),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(crosses.evaluate(null));
+
+        crosses = FF.crosses(FF.literal(GEOM_CROSSES),FF.literal(RIGHT_GEOMETRY));
+        assertTrue(crosses.evaluate(null));
+
+        crosses = FF.crosses(FF.literal(GEOM_DISTANCE_1),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(crosses.evaluate(null));
+    }
+
+    @Test
+    public void testDWithin() {
+        //we can not test units while using jts geometries
+
+        DWithin within = FF.dwithin(FF.literal(RIGHT_GEOMETRY), FF.literal(GEOM_DISTANCE_1),
1.5d, "m");
+        assertTrue(within.evaluate(null));
+
+        within = FF.dwithin(FF.literal(RIGHT_GEOMETRY), FF.literal(GEOM_DISTANCE_3), 1.5d,
"m");
+        assertFalse(within.evaluate(null));
+    }
+
+    @Test
+    public void testDisjoint() {
+        Disjoint disjoint = FF.disjoint(FF.literal(GEOM_CONTAINS),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(disjoint.evaluate(null));
+
+        disjoint = FF.disjoint(FF.literal(GEOM_CROSSES),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(disjoint.evaluate(null));
+
+        disjoint = FF.disjoint(FF.literal(GEOM_DISTANCE_1),FF.literal(RIGHT_GEOMETRY));
+        assertTrue(disjoint.evaluate(null));
+    }
+
+    @Test
+    public void testEquals() {
+        Equals equal = FF.equal(FF.literal(GEOM_CONTAINS),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(equal.evaluate(null));
+
+        equal = FF.equal(FF.literal(GEOM_CROSSES),FF.literal(RIGHT_GEOMETRY));
+        assertFalse(equal.evaluate(null));
+
+        equal = FF.equal(FF.literal(GF.createGeometry(RIGHT_GEOMETRY)),FF.literal(RIGHT_GEOMETRY));
+        assertTrue(equal.evaluate(null));
+    }
+
+    @Test
+    public void testIntersect() {
+        Intersects intersect = FF.intersects(FF.literal(GEOM_CONTAINS), FF.literal(RIGHT_GEOMETRY));
+        assertTrue(intersect.evaluate(null));
+
+        intersect = FF.intersects(FF.literal(GEOM_CROSSES), FF.literal(RIGHT_GEOMETRY));
+        assertTrue(intersect.evaluate(null));
+
+        intersect = FF.intersects(FF.literal(GEOM_INTERSECT), FF.literal(RIGHT_GEOMETRY));
+        assertTrue(intersect.evaluate(null));
+
+        intersect = FF.intersects(FF.literal(GEOM_DISTANCE_1), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(intersect.evaluate(null));
+
+        intersect = FF.intersects(FF.literal(GEOM_DISTANCE_3), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(intersect.evaluate(null));
+    }
+
+    @Test
+    public void testOverlaps() {
+        Overlaps overlaps = FF.overlaps(FF.literal(GEOM_CONTAINS), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(overlaps.evaluate(null));
+
+        overlaps = FF.overlaps(FF.literal(GEOM_DISTANCE_1), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(overlaps.evaluate(null));
+
+        overlaps = FF.overlaps(FF.literal(GEOM_CROSSES), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(overlaps.evaluate(null));
+
+        overlaps = FF.overlaps(FF.literal(GEOM_INTERSECT), FF.literal(RIGHT_GEOMETRY));
+        assertTrue(overlaps.evaluate(null));
+    }
+
+    @Test
+    public void testTouches() {
+        Touches touches = FF.touches(FF.literal(GEOM_CONTAINS), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(touches.evaluate(null));
+
+        touches = FF.touches(FF.literal(GEOM_CROSSES), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(touches.evaluate(null));
+
+        touches = FF.touches(FF.literal(GEOM_DISTANCE_1), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(touches.evaluate(null));
+
+        touches = FF.touches(FF.literal(GEOM_TOUCHES), FF.literal(RIGHT_GEOMETRY));
+        assertTrue(touches.evaluate(null));
+    }
+
+    @Test
+    public void testWithin() {
+        Within within = FF.within(FF.literal(GEOM_CONTAINS), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(within.evaluate(null));
+
+        within = FF.within(FF.literal(GEOM_CROSSES), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(within.evaluate(null));
+
+        within = FF.within(FF.literal(GEOM_DISTANCE_1), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(within.evaluate(null));
+
+        within = FF.within(FF.literal(GEOM_TOUCHES), FF.literal(RIGHT_GEOMETRY));
+        assertFalse(within.evaluate(null));
+
+        within = FF.within(FF.literal(RIGHT_GEOMETRY), FF.literal(GEOM_CONTAINS) );
+        assertTrue(within.evaluate(null));
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
index d4581ac..12cf52e 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/test/suite/FeatureTestSuite.java
@@ -59,6 +59,7 @@ import org.junit.runners.Suite;
     org.apache.sis.filter.ComparisonFunctionTest.class,
     org.apache.sis.filter.BetweenFunctionTest.class,
     org.apache.sis.filter.LikeFunctionTest.class,
+    org.apache.sis.filter.SpatialFunctionTest.class,
     org.apache.sis.filter.TemporalFunctionTest.class,
     org.apache.sis.filter.SQLMMTest.class,
     org.apache.sis.internal.feature.AttributeConventionTest.class,


Mime
View raw message