sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ama...@apache.org
Subject [sis] 32/45: feat(Feature): add naïve implementation of ST_Intersects
Date Tue, 12 Nov 2019 16:44:59 GMT
This is an automated email from the ASF dual-hosted git repository.

amanin pushed a commit to branch refactor/sql-store
in repository https://gitbox.apache.org/repos/asf/sis.git

commit ebcb1d58a78c09717101e4a0b6d55f8641dd31ca
Author: Alexis Manin <amanin@apache.org>
AuthorDate: Wed Oct 9 18:13:24 2019 +0200

    feat(Feature): add naïve implementation of ST_Intersects
---
 .../java/org/apache/sis/filter/ST_Envelope.java    |  10 +-
 .../java/org/apache/sis/filter/ST_Intersects.java  | 114 +++++++++++++++++++++
 .../test/java/org/apache/sis/filter/SQLMMTest.java |  89 +++++++++++++++-
 3 files changed, 209 insertions(+), 4 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
index 98990ae..4178baf 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Envelope.java
@@ -1,5 +1,6 @@
 package org.apache.sis.filter;
 
+import java.util.Collections;
 import java.util.function.Function;
 
 import org.opengis.feature.AttributeType;
@@ -8,6 +9,7 @@ import org.opengis.feature.PropertyType;
 import org.opengis.filter.expression.Expression;
 import org.opengis.filter.expression.Literal;
 import org.opengis.geometry.Envelope;
+import org.opengis.geometry.Geometry;
 import org.opengis.geometry.MismatchedDimensionException;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 
@@ -77,7 +79,7 @@ public class ST_Envelope extends AbstractFunction implements FeatureExpression
{
             }
 
             result = new ImmutableEnvelope(tmpResult);
-            resultType = new DefaultAttributeType(null, Envelope.class, 1, 1, null);
+            resultType = new DefaultAttributeType(Collections.singletonMap("name", "ST_Envelope"),
Envelope.class, 1, 1, null);
         }
 
         @Override
@@ -154,6 +156,12 @@ public class ST_Envelope extends AbstractFunction implements FeatureExpression
{
                     .orElseThrow(() -> new IllegalArgumentException("No geometry provider
found to read WKT"));
         }
 
+        // First, we check if the envelope is already available. If not, we try to compute
it.
+        if (value instanceof Geometry) {
+            final Envelope env = ((Geometry) value).getEnvelope();
+            if (env != null) return env;
+        }
+
         return Geometries.getEnvelope(value);
     }
 }
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java
new file mode 100644
index 0000000..407d291
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ST_Intersects.java
@@ -0,0 +1,114 @@
+package org.apache.sis.filter;
+
+import java.util.function.Predicate;
+
+import org.opengis.filter.FilterVisitor;
+import org.opengis.filter.expression.Expression;
+import org.opengis.filter.expression.Literal;
+import org.opengis.filter.spatial.Intersects;
+import org.opengis.geometry.Geometry;
+
+import org.apache.sis.internal.feature.GeometryWrapper;
+
+import org.locationtech.jts.geom.prep.PreparedGeometry;
+import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
+
+import static org.apache.sis.util.ArgumentChecks.ensureNonEmpty;
+import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
+
+/**
+ * TODO: check CRS
+ * TODO: refine once Geometry API is stable.
+ */
+public class ST_Intersects implements Intersects {
+
+    public static final String NAME = "ST_Intersect";
+
+    final Expression left;
+    final Expression right;
+
+    private final Predicate intersects;
+
+    public ST_Intersects(Expression[] parameters) {
+        ensureNonEmpty("Parameters", parameters);
+        if (parameters.length != 2) throw new IllegalArgumentException("2 parameters are
expected for intersection, but "+parameters.length+" are provided");
+
+        left = parameters[0];
+        right = parameters[1];
+        ensureNonNull("Left operand", left);
+        ensureNonNull("Right operand", right);
+        if (left instanceof Literal && right instanceof Literal) {
+            intersects = constantOp((Literal) left, (Literal) right);
+        } else if (left instanceof Literal) {
+            intersects = intersect(right, (Literal) left);
+        } else if (right instanceof Literal) {
+            intersects = intersect(left, (Literal) right);
+        } else intersects = this::nonOptimizedIntersects;
+    }
+
+    private boolean nonOptimizedIntersects(Object candidate) {
+        final Object leftEval = left.evaluate(candidate);
+        final Object rightEval = right.evaluate(candidate);
+        if (leftEval == null || rightEval == null) return false;
+        return toJTS(leftEval).intersects(toJTS(rightEval));
+    }
+
+    private static org.locationtech.jts.geom.Geometry toJTS(Object value) {
+        if (value instanceof GeometryWrapper) value = ((GeometryWrapper) value).geometry;
+        if (value instanceof org.locationtech.jts.geom.Geometry) return (org.locationtech.jts.geom.Geometry)
value;
+        throw new UnsupportedOperationException("Unsupported geometry type: "+value.getClass().getCanonicalName());
+    }
+
+    private Predicate constantOp(Literal left, Literal right) {
+        final boolean result = left.getValue() != null
+                && right.getValue() != null
+                && toJTS(left.getValue()).intersects(toJTS(right.getValue()));
+        return it -> result;
+    }
+
+    private static Predicate intersect(Expression left, Literal right) {
+        Object value = right.getValue();
+        ensureNonNull("Literal value", value);
+        // TODO: make more consistent strategy once Geometry API is stable.
+        if (value instanceof GeometryWrapper) value = ((GeometryWrapper) value).geometry;
+        if (value instanceof org.locationtech.jts.geom.Geometry) {
+            final PreparedGeometry pg = new PreparedGeometryFactory().create((org.locationtech.jts.geom.Geometry)
value);
+            return it -> {
+                Object val = left.evaluate(it);
+                if (val == null) return false;
+                return pg.intersects(toJTS(val));
+            };
+        } else if (value instanceof Geometry) {
+            final Geometry geom = (Geometry) value;
+            return it -> {
+                final Geometry newVal = left.evaluate(it, Geometry.class);
+                if (newVal == null) {
+                    final Object testVal = left.evaluate(it);
+                    if (testVal == null) return false;
+                    throw new UnsupportedOperationException("Unsupported geometry type: "+testVal.getClass().getCanonicalName());
+                }
+                return geom.intersects(newVal);
+            };
+        } else throw new UnsupportedOperationException("Unsupported geometry type: "+value.getClass().getCanonicalName());
+    }
+
+    @Override
+    public Expression getExpression1() {
+        return left;
+    }
+
+    @Override
+    public Expression getExpression2() {
+        return right;
+    }
+
+    @Override
+    public boolean evaluate(Object object) {
+        return intersects.test(object);
+    }
+
+    @Override
+    public Object accept(FilterVisitor visitor, Object extraData) {
+        return visitor.visit(this, extraData);
+    }
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java b/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
index 9929d04..75a2fbc 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/filter/SQLMMTest.java
@@ -21,18 +21,27 @@ import org.opengis.feature.FeatureType;
 import org.opengis.filter.FilterFactory2;
 import org.opengis.filter.expression.Expression;
 import org.opengis.filter.expression.Function;
+import org.opengis.filter.expression.Literal;
+import org.opengis.filter.expression.PropertyName;
+import org.opengis.geometry.Envelope;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 
 import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.util.NullArgumentException;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
 import org.locationtech.jts.geom.Point;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -40,6 +49,9 @@ import static org.junit.Assert.fail;
  * @author Johann Sorel (Geomatys)
  */
 public class SQLMMTest extends TestCase {
+
+    private static final String P_NAME = "geom";
+
     /**
      * The factory to use for creating the objects to test.
      */
@@ -78,7 +90,7 @@ public class SQLMMTest extends TestCase {
 
             //check result
             final Object newGeom = fct.evaluate(feature);
-            Assert.assertTrue(newGeom instanceof Point);
+            assertTrue(newGeom instanceof Point);
             final Point trs = (Point) newGeom;
             Assert.assertEquals(outCrs, trs.getUserData());
             Assert.assertEquals(30.0, trs.getX(), 0.0);
@@ -90,7 +102,7 @@ public class SQLMMTest extends TestCase {
 
             //check result
             final Object newGeom = fct.evaluate(feature);
-            Assert.assertTrue(newGeom instanceof Point);
+            assertTrue(newGeom instanceof Point);
             final Point trs = (Point) newGeom;
             Assert.assertEquals(outCrs, trs.getUserData());
             Assert.assertEquals(30.0, trs.getX(), 0.0);
@@ -98,6 +110,7 @@ public class SQLMMTest extends TestCase {
         }
     }
 
+    @Test
     public void ST_Envelope() {
         try {
             new ST_Envelope(new Expression[2]);
@@ -113,6 +126,76 @@ public class SQLMMTest extends TestCase {
             // expected behavior
         }
 
-        // TODO: update SIS version then add test cases.
+        final LineString pt = gf.createLineString(new Coordinate[]{
+                new Coordinate(12, 3.3),
+                new Coordinate(13.1, 4.4),
+                new Coordinate(12.02, 5.7)
+        });
+        ST_Envelope operator = new ST_Envelope(new Expression[]{factory.literal(pt)});
+        final GeneralEnvelope expectedEnv = new GeneralEnvelope(2);
+        expectedEnv.setEnvelope(12, 3.3, 13.1, 5.7);
+        Envelope evaluated = (Envelope) operator.evaluate(null);
+        assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(),
evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+        evaluated = (Envelope) operator.evaluate(null);
+        assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(),
evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+
+        // After testing literal data, we'll now try to extract data from a feature.
+        final Feature f = mock();
+        f.setPropertyValue(P_NAME, pt);
+        operator = new ST_Envelope(new Expression[]{factory.property(P_NAME)});
+        evaluated = (Envelope) operator.evaluate(f);
+        assertTrue(String.format("Bad result:%nExpected: %s%nBut got: %s", expectedEnv.toString(),
evaluated.toString()), expectedEnv.equals(evaluated, 1e-10, false));
+    }
+
+    @Test
+    public void ST_Intersects() {
+        try {
+            new ST_Intersects(null);
+            fail("ST_Intersects operator should accept exactly 2 parameters");
+        } catch (NullArgumentException e) {
+            // expected behavior
+        }
+
+        try {
+            new ST_Intersects(new Expression[3]);
+            fail("ST_Intersects operator should accept exactly 2 parameters");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        final Coordinate start = new Coordinate(0, 0.1);
+        final Coordinate second = new Coordinate(1.2, 0.2);
+        final LinearRing ring = gf.createLinearRing(new Coordinate[]{
+                start, second, new Coordinate(0.7, 0.8), start
+        });
+
+        final Literal lring = factory.literal(ring);
+        ST_Intersects st = new ST_Intersects(new Expression[]{factory.literal(gf.createPoint(new
Coordinate(2, 4))), lring});
+        // Ensure argument nullity does not modify behavior
+        assertFalse("Unexpected intersection", st.evaluate(null));
+        assertFalse("Unexpected intersection", st.evaluate(new Object()));
+
+        // Border should intersect
+        final Feature f = mock();
+        f.setPropertyValue(P_NAME, gf.createPoint(second));
+        final PropertyName geomName = factory.property(P_NAME);
+        st = new ST_Intersects(new Expression[]{geomName, lring});
+        assertTrue("Border point should intersect triangle", st.evaluate(f));
+        // Ensure inverting expression does not modify behavior.
+        st = new ST_Intersects(new Expression[]{lring, geomName});
+        assertTrue("Border point should intersect triangle", st.evaluate(f));
+
+        // TODO: add CRS checking tests.
+    }
+
+    /**
+     *
+     * @return A feature with a single property of {@link Object any} type named after
+     */
+    private static Feature mock() {
+        final FeatureTypeBuilder ftb = new FeatureTypeBuilder().setName("mock");
+        ftb.addAttribute(Object.class).setName(P_NAME);
+        final FeatureType mockType = ftb.build();
+        return mockType.newInstance();
     }
 }


Mime
View raw message